minad / cape

🦸cape.el - Completion At Point Extensions
GNU General Public License v3.0
592 stars 21 forks source link

Legitimate file name at point #9

Closed jdtsmith closed 2 years ago

jdtsmith commented 2 years ago

One perennial bother with file name completers like cape-file-capf is that a 'filename is basically anything, even a period in a comment or string, which will match lots of files "out of the blue", especially when using aggressive completion styles like partial-completion.

It would be nice to be able to require an actual directory in the putative filename at point before attempting completion. Something like (if (file-name-directory file-name-at-point .... This could be done using a configurable predicate to test the thing-at-point before attempting completion. Perhaps this "user-predicate with proposed prefix" could be generalized to all of the cape's.

minad commented 2 years ago

Perhaps this "user-predicate with proposed prefix" could be generalized to all of the cape's.

The idea for cape would be to implement a cape-capf-with-predicate wrapper which runs first the orignal capf and then calls the predicate on the prefix. We can add all these nice transformers. But it is questionable if there is really a need for this. Do we have more capfs where you would want a configurable predicate?

minad commented 2 years ago

cape-capf-with-predicate seems at least somewhat useful. Example:

(cape-capf-with-predicate #'my-src-capf :predicate #'check-if-within-org-src-block)
minad commented 2 years ago

Hmm, thinking about it, I doubt the usefulness of the predicate. What would be more useful are lifted operators. But these operators are even too generic for Cape.

(defun lifted-or (f g)
  (lambda () (or (funcall f) (funcall g))))

(defun lifted-and (f g)
  (lambda () (and (funcall f) (funcall g))))

(defun lifted-if (f g h)
  (lambda () (if (funcall f) (funcall g) (funcall h))))
jdtsmith commented 2 years ago

I can think of overriding CAPF’s to provide more specificity (as in my explicit file example), or even switching among CAPF’s in a multi-mode file. It would also be an easy way for a user to “blackball” a whole class of prefixes, if they are too noisy.

minad commented 2 years ago

I can think of overriding CAPF’s to provide more specificity (as in my explicit file example), or even switching among CAPF’s in a multi-mode file.

Yes, but for this use case one should use the trivial lifted-and from above. See also polymode.

It would also be an easy way for a user to “blackball” a whole class of prefixes, if they are too noisy.

I agree. This is the exact use case of cape-capf-with-predicate. But I find this is a bit too special or too much a micro configuration. If users want to perform these kinds of tweaks they can as well write their own capf-with-predicate function in their user configuration. If it turns out that such a combinator would be widely useful we can add it here. But I think we need a bit more experience with what is needed.

jdtsmith commented 2 years ago

cape-capf-with-predicate was indeed quite trivial.

But, to quote Yoda, "there is another." What about the PRED argument a completion table receives? I've never understood who passes that function, or how a user could conveniently inject their own PRED into the completion chain.

I have a concrete case for this: in elisp-mode with orderless, :keywords from the obarray match on basically everything I type, since there are so many of them:

image

I want to fall back on a style that only matches keywords if the text begins with a :. But my predicate would need to know both the candidate text and the in-buffer (aka "prefix") text.

minad commented 2 years ago

What about the PRED argument a completion table receives? I've never understood who passes that function, or how a user could conveniently inject their own PRED into the completion chain.

  1. For completing-read and completion-in-region you can pass the predicate directly.
  2. For Capfs, the :predicate key from the extra metadata is used:

https://github.com/minad/cape/blob/d3d9d926ac7e860bd5e919f924abcd76f3fca790/cape.el#L319-L325

I have a concrete case for this: in elisp-mode with orderless, :keywords from the obarray match on basically everything I type, since there are so many of them:

Indeed, this is a reasonable use case for such a capf predicate. But given the complication that you need access to both the prefix and the candidate, maybe it is not even feasible? I suggest to implement your own custom capfs in that case. Or another idea could be a capf-with-filter, which can be used to filter out bad candidates. There are many possible combinators. I will create a separate issue where we track ideas.

jdtsmith commented 2 years ago

I was able to accomplish my desired elisp-mode keyword-trimming pretty simply with:

  (defun my/ignore-elisp-keywords (cand)
    (or (not (keywordp cand))
    (eq (char-after (car completion-in-region--data)) ?:)))

  (defun my/setup-elisp ()
    (setq-local completion-at-point-functions
        `(,(cape-capf-with-properties
            #'elisp-completion-at-point
            :predicate #'my/ignore-elisp-keywords)
          cape-file)))
  (add-hook 'emacs-lisp-mode-hook #'my/setup-elisp))

My goodness the customization opportunities are quite endless. You could imagine people specializing in packages which are just "really good pre-configured and trimmed super-capfs for various modes".

minad commented 2 years ago

My goodness the customization opportunities are quite endless. You could imagine people specializing in packages which are just "really good pre-configured and trimmed super-capfs for various modes".

Lol, let's not go there! It is a paradise for Emacs tweakers...

Thanks for showing the predicate example. This makes the cape-filter-capf unnecessary. But note that the completion function may specify a predicate out of the box! If you overwrite the predicate this could break the functionality of the completion table. So cape-filter-capf should compose the predicates.

minad commented 2 years ago

I added cape-capf-with-predicate. See https://github.com/minad/cape/commit/2008d809b6414c929227cec081673f2b534824e9.

Your example can be adjusted:

  (defun my/ignore-elisp-keywords (cand)
    (or (not (keywordp cand))
    (eq (char-after (car completion-in-region--data)) ?:)))

  (defun my/setup-elisp ()
    (setq-local completion-at-point-functions
        `(,(cape-capf-with-predicate
            #'elisp-completion-at-point
            #'my/ignore-elisp-keywords)
          cape-file)))
(add-hook 'emacs-lisp-mode-hook #'my/setup-elisp)
jdtsmith commented 2 years ago

Yes, that's definitely better. Tried and it works perfectly, thanks. Once this settles I can put a few examples in the cape wiki if you make one.

minad commented 2 years ago

Yes, that's definitely better. Tried and it works perfectly, thanks. Once this settles I can put a few examples in the cape wiki if you make one.

Maybe keep them in the Corfu wiki for now? Otherwise it is getting a bit out of hand for users, better to have things bundled together. There is already significant stuff in the Vertico and the Consult wiki. Now I added the Corfu wiki with a few basic tips. And of it turns out later that most of the stuff is actually Cape-specific only I will add an extra Cape wiki.

I should add - while Cape is totally frontend agnostic and it will stay like this (except maybe a potential async extension), it is still mostly interesting for Corfu users and default completion users. Cape to Corfu is a bit like Consult to Vertico, but Cape should be much smaller and more focused.