Emacs autocomplete

Has anyone managed to get emacs autocompletion on tidal code? I gave a quick try running lsp-haskell but didn't get anything to work properly yet.

1 Like

I'm using company but the way it does it is not entirely satisfying (I sometimes would like it to autocomplete things but it doesn't). I've been putting off digging into that, so, if you make it work, I'm interested!

Still an issue? I also have company mode running with an extended version of

2 Likes

In case anyone is interested, here is my Emacs configuration for that:

(use-package company-tidal-backend
  :load-path "~/path/to/folder/where/company/file/lives"
  :init (add-to-list 'company-backends 'company-tidal-backend)
)

In my case I also had to add

(company-box-mode -1)

because otherwise the autocompletion display was faulty.

Unfortunately my Emacs has somehow problems with Tidal. Even though I am unhappy with that, I gave Atom a try, which for now seems to work without problems.

I'm trying to get this to work in doom emacs.
I added (package! company) to my packages.el and ran doom sync
I saved the company-tidal-backend.el to may system and referred to it in the

(use-package company-tidal-backend
  :load-path "~/path/to/folder/where/company/file/lives"
  :init (add-to-list 'company-backends 'company-tidal-backend)
)

in my packages.el but no go

how can I do this?

I recently got on the Consult+Marginalia+Corfu+Cape+Embark+orderless bandwagon, from Ivy+Counsel+Swiper. How cool of me! :dark_sunglasses:

Anyway that inspired me to try to finally make some kind of progress on the autocompletetion front. I wrote one big list, a complete-at-point-function, and a hook. However, it does not work.

Except sometimes it does.

I've confused myself.

1 Like

It isn't a pull request yet, but here's an early share if someone wants to give some feedback and/or advice. @pulu was already really helpful by suggesting trying to goop up symbols from Tidal.Sound via a second GHCi process, and pointing my attention to Tidal.Sound.Context which seems like the right place to get them from.

Anyway, the three things are

  1. A collection of symbols as completion candidates
  2. A completion-at-point function which consumes the collection of completion candidates
  3. Patching up the completion-at-point to completion hook completion-at-point-functions

I'll try to sort this out into a PR for someone to review, reject etc, but here is a sketch more-or-less.

1. Collect completion candidates

I first just typed the entire (well, almost) documentation into one big list of candidates. Then it developed into this crude function, which requests symbols of Sound.Tidal.Context symbols from GHCi (trigger warning if you are actually good at Emacs Lisp)

(defun tidal-get-completion-symbols ()
  "Get completion symbols from Sound.Tidal.Context"
  (let* ((tidal-browse (shell-command-to-string
                        (combine-and-quote-strings
                         (append
                          (cons tidal-interpreter tidal-interpreter-arguments)
                          '("-e" ":browse Sound.Tidal.Context")))))
         (tidal-symbols-with-ns (mapcar
                                 #'(lambda (s)
                                     (car (split-string s)))
                                 (cl-remove-if #'(lambda (s)
                                                   (string-prefix-p " " s))
                                               (split-string tidal-browse "\n"))))
         (tidal-names (mapcar
                       #'(lambda (s)
                           (string-remove-suffix ")" (car (reverse (split-string s "\\.")))))
                       (cl-remove-if nil tidal-symbols-with-ns)))
         (tidal-symbols (cl-remove-if
                         #'(lambda (s)
                             (string-prefix-p "_" s))
                         tidal-names)))
    tidal-symbols))

(setq tidal-keywords (tidal-get-completion-symbols))

That excludes the things which start with an underscore, because I don't know what they are, and might be meant for internal use only by the computer. Another things which is not there, but which would be very welcome but I don't yet know where to get it, is the list of sounds from `SuperDirt.soundlibrary on the SuperCollider side.

2. Completion at point function

This thing finds candidates. I have it trigger after I've typed three characters, I could use maybe something more aggressive like already two characters, when livecoding. Or bind to TAB to conjure completions.

(defun tidal-completion-at-point ()
  "Tidal Cycles function for `completion-at-point-functions`."
  (interactive)
  (let* (
         (bds (bounds-of-thing-at-point 'symbol))
         (start (car bds))
         (end (cdr bds)))
    (list start end tidal-keywords . nil)))

3. Connect to native Emacs completion framework

I use the Consult+Corfu thing, which is built on. the native Emacs completion framework. Company is something else. This works for the former.

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

Sorry for brutal code, that's the best I could do in Elisp right now. Also I know next to nothing about Haskell. I'm learning :slight_smile: Maybe I'll be able to get some nice annotations going on too to get more documentation about arguments, types, one-line descriptions involved, not just symbol names. That's next level!

Good luck with the above if anybody wants to try already.

1 Like

cool! it works nicely with company too, via the company-capf backend :slight_smile: (i havent tried company-tidal-backend, mainly relied on company-dabbrev with a lot of files always open lol)

during our brainstorming yesterday, i found out about ghci's :complete command, which has a simpler output format than :browse and returns absolutely everything that is in scope. it requires a prefix string to start completing from, but it can be empty:

$ ghci -e 'import Sound.Tidal.Context' -e ':complete repl ""'
5114 5114 ""
"!!"
"#"
"$"
"$!"
...

in my Tidal coding i use plenty of standard Haskell functions from Prelude and it would be nice to have completion for those as well. so just now i wrote my own variant of tidal-get-completion-symbols:

(defun tidal-get-completion-symbols ()
  "Get completion symbols from Prelude and Sound.Tidal.Context"
  (let* ((tidal-complete-output (shell-command-to-string
                                 (combine-and-quote-strings
                                  (append (cons tidal-interpreter tidal-interpreter-arguments)
                                          '("-e" "import Sound.Tidal.Context"
                                            "-e" ":complete repl \"\"")))))
         (parsed-completions (remq nil
                                   (mapcar #'(lambda (line)
                                               (if (string-match "^\\\"\\(.+\\)\\\"$" line)
                                                   (match-string 1 line)))
                                           ;; first line is status
                                           (cdr (string-split tidal-complete-output "\n"))))))
    parsed-completions))

seems to work!

EDIT: filtered out nils

1 Like

Joooo fantastic, thanks! My hope/dream/desire is to snag that other input from :browse into completion annotations and/or eldoc eventually. I don't know if always doing a :type would be a good idea instead, or not. Maybe I will need to learn about reading stuff from other buffers in Emacs... so as not to form ghci for every single item in a list of candidates...

Anyway that :complete thing indeed seems totally designed for purpose of just getting the list of candidates.

fair enough! i think personally type annotations would be the point where i bite the bullet and start setting up eglot + HLS :grin:

this is not necessarily a good idea, but it is a fun hack: you can get all the type signatures in a single additional ghci invocation! :smile: (some OSes might hit command line length limits...)

given an already populated tidal-keywords:

(cl-flet* ((first-char-category (str) (get-char-code-property (aref str 0) 'general-category))
           ;; operators must be wrapped in parens
           (operator-p (name) (not (or (string-prefix-p "_" name)
                                       (eq ?L (aref (symbol-name (first-char-category name)) 0)))))
           (format-name (name) (if (operator-p name)
                                   (concat "(" name ")")
                                 name))
           ;; can't get the type of a type
           ;; also exclude ~, the type equality constraint operator
           (type-p (name) (or (eq 'Lu (first-char-category name))
                              (member name '("~"))))
           ;; some type signatures are split across multiple lines
           ;; the continuation lines are indented -> easy to detect
           (collapse-multiline-sigs (str) (replace-regexp-in-string "\n[[:space:]]+" " " str)))
  (split-string
   (collapse-multiline-sigs
    (shell-command-to-string
     (combine-and-quote-strings
      (apply #'append
             `(,tidal-interpreter)
             tidal-interpreter-arguments
             ;; Tidal defines these identifiers too, so hide the Prelude ones to avoid ambiguous occurrence
             '("-e" "import Prelude hiding ((<*), (*>))")
             '("-e" "import Sound.Tidal.Context")
             (cl-loop for name in tidal-keywords
                      when (not (type-p name))
                      collect `("-e" ,(concat ":t " (format-name name))))))))
   "\n"))