KaratasFurkan / .emacs.d

My literate Emacs configuration
167 stars 14 forks source link

Emacs-based integrated document preparation environment characterized by coding/command completion, human text writing helper, and snippet insertion collaborating together in a similar way. #1

Closed hongyi-zhao closed 3 years ago

hongyi-zhao commented 3 years ago

On Ubuntu 20.04, based on your wonderful code snippets here, I tried with the following company-wordfreq configuration:

(use-package company-wordfreq
  :straight (:host github :repo "johannes-mueller/company-wordfreq.el")
  :commands fk/company-wordfreq-mode
  :custom
  (ispell-local-dictionary "english")
  :config
  (define-minor-mode fk/company-wordfreq-mode
    "Suggest words by frequency."
    :global nil
    (if fk/company-wordfreq-mode
        (progn
          (setq-local company-backends-backup company-backends)
          (setq-local company-transformers-backup company-transformers)
          (setq-local company-backends '(company-wordfreq))
          (setq-local company-transformers nil))
      (setq-local company-backends company-backends-backup)
      (setq-local company-transformers company-transformers-backup))))

I test the above setting in Emacs with the following workflow:

C-\ british RET
M-x fk/company-wordfreq-mode RET

Then input some English words, but there is no drop-down menu which suggests me possible words as I type, as shown below:

image

Any hints for this problem will be highly appreciated.

Regards, HY

KaratasFurkan commented 3 years ago

Hi, have you installed and activated company-mode? company-wordfreq is actually just a backend for company-mode.

Also you need to download word list as described in the documentation:

M-x company-wordfreq-download-list
hongyi-zhao commented 3 years ago

Hi, have you installed and activated company-mode?

Really, the following commands do the trick:

M-x company-mode RET
M-x fk/company-wordfreq-mode RET

See the drop-down menu on the screenshot below, which is triggered by TAB when the number of the input characters greater than or equal to 3:

image

M-x company-wordfreq-download-list

I've downloaded the dictionary file:

$ ls -l ~/.emacs.d/wordfreq-dicts/
total 392
-rw-rw-r-- 1 werner werner 400440 Jun 19 12:12 english.txt

But I want to know how to let the customized function, i.e., fk/company-wordfreq-mode, automatically activate company-mode on the corresponding buffer for me.

KaratasFurkan commented 3 years ago

You can edit the minor mode directly:

(define-minor-mode fk/company-wordfreq-mode
  "Suggest words by frequency."
  :global nil
  (if fk/company-wordfreq-mode
      (progn
        (company-mode)  ;; <---- ACTIVATE COMPANY MODE
        (setq-local company-backends-backup company-backends)
        (setq-local company-transformers-backup company-transformers)
        (setq-local company-backends '(company-wordfreq))
        (setq-local company-transformers nil))
    (setq-local company-backends company-backends-backup)
    (setq-local company-transformers company-transformers-backup)))

or add a hook if you want to make it from outside:

 (add-hook 'fk/company-wordfreq-mode-hook 'company-mode)

Also, I recommend these settings for company to see dropdown immediately after first character:

 (setq company-idle-delay 0)
 (setq company-minimum-prefix-length 1)
KaratasFurkan commented 3 years ago

I closed it by mistake.

hongyi-zhao commented 3 years ago

Got it. Wonderful solution. Thank you very much.

KaratasFurkan commented 3 years ago

You're welcome :+1:

hongyi-zhao commented 3 years ago

I previously only know the following way to activate the company mode, which is invalid for this case due to the customized function works with a minor-mode:

(use-package company)
(add-hook 'after-init-hook 'global-company-mode)

Here, you've shown me the tip of the iceberg of Elisp's sophistication and power.

Other relevant questions/comments:

  1. Can I trigger a similar dropdown menu for yasnippet with company for the snippet insertion, as describe by the issue I filed here?

  2. For fine and modular complicated control, it seems that the global-* options for many packages are not good/graceful, and even error methods.

Thank you again.

KaratasFurkan commented 3 years ago

Actually I use global-company-mode too and it works fine I don't know why it didn't work for you.

How do you want to see snippets exactly? Mixed with company-wordfreq? Mixed with code completion? or just show it manually?

If you want to see dropdown manually, just type some letters and run M-x company-yasnippet. Or you can see all the snippets related to your current buffer with M-x yas-describe-tables.

hongyi-zhao commented 3 years ago

How do you want to see snippets exactly? Mixed with company-wordfreq? Mixed with code completion? or just show it manually?

What about let code completion, human language texts completion, and snippet completion work similarly?

If you want to see dropdown manually, just type some letters and run M-x company-yasnippet.

Which letters will trigger which snippets?

KaratasFurkan commented 3 years ago

Can you try copy pasting these:

(defun fk/company-backend-with-yas (backend)
    "Add ':with company-yasnippet' to the given company backend."
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend)
                  backend
                (list backend))
              '(:with company-yasnippet))))

  (defun fk/company-smart-snippets (fn command &optional arg &rest _)
    "Do not show yasnippet candidates after dot."
    ;;Source:
    ;;https://www.reddit.com/r/emacs/comments/7dnbxl/how_to_temporally_filter_companymode_candidates/
    (unless (when (and (equal command 'prefix) (> (point) 0))
              (let* ((prefix (company-grab-symbol))
                     (point-before-prefix (if (> (- (point) (length prefix) 1) 0)
                                              (- (point) (length prefix) 1)
                                            1))
                     (char (buffer-substring-no-properties point-before-prefix (1+ point-before-prefix))))
                (string= char ".")))
      (funcall fn command arg)))

  ;; TODO: maybe show snippets at first?
  (defun fk/company-enable-snippets ()
    "Enable snippet suggestions in company by adding ':with
company-yasnippet' to all company backends."
    (interactive)
    (setq company-backends (mapcar 'fk/company-backend-with-yas company-backends))
    (advice-add 'company-yasnippet :around 'fk/company-smart-snippets))

  (fk/company-enable-snippets)  ;; <------- ACTIVATE SNIPPET COMPLETION
hongyi-zhao commented 3 years ago

Based on your comments above, I tried to test with the following code snippet:

(use-package company)
(setq company-idle-delay 0)
(setq company-minimum-prefix-length 1)

(use-package company-wordfreq
  :straight (:host github :repo "johannes-mueller/company-wordfreq.el")
  :commands my/company-wordfreq-mode
  :custom
  (ispell-local-dictionary "english")
  :config
  (define-minor-mode my/company-wordfreq-mode
    "Suggest words by frequency."
    :global nil
    (if my/company-wordfreq-mode
        (progn
          ;https://github.com/KaratasFurkan/.emacs.d/issues/1#issuecomment-864426970
          (company-mode)  ;; <---- ACTIVATE COMPANY MODE
          (setq-local company-backends-backup company-backends)
          (setq-local company-transformers-backup company-transformers)
          (setq-local company-backends '(company-wordfreq))
          (setq-local company-transformers nil))
      (setq-local company-backends company-backends-backup)
      (setq-local company-transformers company-transformers-backup))))

(add-hook 'latex-mode-hook 'my/company-wordfreq-mode) 

(use-package yasnippet)

(defun my/company-backend-with-yas (backend)
    "Add ':with company-yasnippet' to the given company backend."
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend)
                  backend
                (list backend))
              '(:with company-yasnippet))))

  (defun my/company-smart-snippets (fn command &optional arg &rest _)
    "Do not show yasnippet candidates after dot."
    ;;Source:
    ;;https://www.reddit.com/r/emacs/comments/7dnbxl/how_to_temporally_filter_companymode_candidates/
    (unless (when (and (equal command 'prefix) (> (point) 0))
              (let* ((prefix (company-grab-symbol))
                     (point-before-prefix (if (> (- (point) (length prefix) 1) 0)
                                              (- (point) (length prefix) 1)
                                            1))
                     (char (buffer-substring-no-properties point-before-prefix (1+ point-before-prefix))))
                (string= char ".")))
      (funcall fn command arg)))

  ;; TODO: maybe show snippets at first?
  (defun my/company-enable-snippets ()
    "Enable snippet suggestions in company by adding ':with
company-yasnippet' to all company backends."
    (interactive)
    (setq company-backends (mapcar 'my/company-backend-with-yas company-backends))
    (advice-add 'company-yasnippet :around 'my/company-smart-snippets))

  (my/company-enable-snippets)  ;; <------- ACTIVATE SNIPPET COMPLETION

I noticed the following problems:

  1. With the above method, when I open a TeX document, my/company-wordfreq-mode will be activated, but I still must first run the following command in order to activate the snippets completion:
    M-x my/company-enable-snippets RET
  2. It seems that my/company-enable-snippets depends on my/company-wordfreq-mode, i.e., the second command must be activated first, otherwise, the first won't work at all.
  3. With the my/company-wordfreq-mode activated, the completion results for you will be as follows:

image

Subsequently, I run M-x my/company-enable-snippets RET, now, the completion results for you will be as follows:

image

I don't know if these are the expected results.

  1. In my current situation, I'm using the LaTeX document preparation environment building on auctex, eglot, texlab, and company with the following configuration, which I've described here and here:
;;; Using AUCTEX from the local Git Repo without installation through straight.
(straight-use-package
 `(auctex :type git :host nil :repo "https://git.savannah.gnu.org/git/auctex.git"
    :pre-build ,(pcase system-type
                (`berkeley-unix '("gmake"))
                (_ '(
                    `("bash" "-c" "cd" ,(straight--repos-dir "auctex"))
                    ("./autogen.sh")
                    ("./configure" "--without-texmf-dir" "--with-lispdir=.") 
                    ("make")
                    )))))

     (setq TeX-data-directory (straight--repos-dir "auctex")
           TeX-lisp-directory TeX-data-directory)                   

; Or set the following variable via custom-set-variables in the opened buffer by the following command.
; M-x describe-variable RET preview-TeX-style-dir RET
;`(preview-TeX-style-dir ,(concat ".:" (straight--repos-dir "auctex") "latex:"))
(setq preview-TeX-style-dir (concat ".:" (straight--repos-dir "auctex") "latex:"))

(load "auctex.el" nil t t) 
(load "preview-latex.el" nil t t)

(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq-default TeX-master nil)

;;; eglot, company, and texlab
(use-package company)
(add-hook 'after-init-hook 'global-company-mode)

(use-package eglot)
;https://tam5917.hatenablog.com/entry/2021/04/01/014719
;; hook into AUCTeX 
(add-hook 'TeX-mode-hook 'eglot-ensure)
;; ensure we don't use digestif server, which is far less than texlab on features and characteristics.
(delete (assoc '(tex-mode context-mode texinfo-mode bibtex-mode)
               eglot-server-programs) eglot-server-programs)
;; ensure texlab is set as the server.
(add-to-list 'eglot-server-programs
             '((latex-mode tex-mode context-mode texinfo-mode bibtex-mode) . (
             "texlab"
             )))
  1. In the current triggered dropdown menu, the up/down arrow key is used to select the different candidates, which is not so convenient due that they are far away from our fingers. The possible improvements might be as follows:
  1. To summarize, I want to achieve the following aims: for a specific major mode, say, (La)TeX document preparation environment based my above configuration, integrate the wordfreq writing helper for normal typesetting, and yasnippet for the major mode specific snippets' insertion automatically. I mean, once I open a (La)TeX document, all the above-mentioned features will work OOTB, rather than requiring me to run the corresponding custom command/function to activate them manually.
KaratasFurkan commented 3 years ago

I don't know if these are the expected results.

Yes they are. my/company-enable-snippets patches current company-backends, since my/company-wordfreq-mode changes company-backends, you need to run my/company-enable-snippets again. This may help:

(add-hook 'my/company-wordfreq-mode-hook 'my/company-enable-snippets)

For the keybindings, you can check the :bind section of my company-mode settings: https://github.com/KaratasFurkan/.emacs.d#company

hongyi-zhao commented 3 years ago

This may help:

Thank you for letting me know the hook chain based method. It does the trick by replacing the following command:

(my/company-enable-snippets)

with:

(add-hook 'my/company-wordfreq-mode-hook 'my/company-enable-snippets)

For the keybindings, you can check the :bind section of my company-mode settings: https://github.com/KaratasFurkan/.emacs.d#company

I noticed the following keyboard binding custom Settings:

    ("RET" . nil)
    ([return] . nil)
    ("C-w" . nil)
    ("TAB" . company-complete-selection)
    ("<tab>" . company-complete-selection)
    ("C-s" . company-complete-selection)  ; Mostly to use during yasnippet expansion
    ("C-n" . company-select-next)
    ("C-p" . company-select-previous))

Why do you set the first three to nil?

KaratasFurkan commented 3 years ago

Because I don't want to select candidate with RET (which is default, so I disabled the default binding). C-w was conflicting with one of my other keybinding so I disabled it too.

hongyi-zhao commented 3 years ago

Thank you for your explanation.

Based on the method described here, considering that the elements in the list are executed in the reverse order by add-hook, I combined the two hooks into one as shown below:

(dolist (fn '(my/company-enable-snippets
              my/company-wordfreq-mode))
  (add-hook 'latex-mode-hook fn))
hongyi-zhao commented 3 years ago

But I still can't figure out how to let the above configurations to work with latex command completion at the same time, as shown below for the configuration and the completion effect screenshot:

(straight-use-package
 `(auctex :type git :host nil :repo "https://git.savannah.gnu.org/git/auctex.git"
    :pre-build ,(pcase system-type
                (`berkeley-unix '("gmake"))
                (_ '(
                    `("bash" "-c" "cd" ,(straight--repos-dir "auctex"))
                    ("./autogen.sh")
                    ("./configure" "--without-texmf-dir" "--with-lispdir=.") 
                    ("make")
                    )))))

     (setq TeX-data-directory (straight--repos-dir "auctex")
           TeX-lisp-directory TeX-data-directory)                   

; Or set the following variable via custom-set-variables in the opened buffer by the following command.
; M-x describe-variable RET preview-TeX-style-dir RET
;`(preview-TeX-style-dir ,(concat ".:" (straight--repos-dir "auctex") "latex:"))
(setq preview-TeX-style-dir (concat ".:" (straight--repos-dir "auctex") "latex:"))

(load "auctex.el" nil t t) 
(load "preview-latex.el" nil t t)

(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq-default TeX-master nil)

(use-package eglot
  :hook 
  (
  (TeX-mode . eglot-ensure)
  )
  :config
(add-to-list 'eglot-server-programs
             '((latex-mode tex-mode context-mode texinfo-mode bibtex-mode) . (
             "texlab"
             ))))

image

When I put all the settings discussed above together as follows:

(straight-use-package
 `(auctex :type git :host nil :repo "https://git.savannah.gnu.org/git/auctex.git"
    :pre-build ,(pcase system-type
                (`berkeley-unix '("gmake"))
                (_ '(
                    `("bash" "-c" "cd" ,(straight--repos-dir "auctex"))
                    ("./autogen.sh")
                    ("./configure" "--without-texmf-dir" "--with-lispdir=.") 
                    ("make")
                    )))))

     (setq TeX-data-directory (straight--repos-dir "auctex")
           TeX-lisp-directory TeX-data-directory)                   

; Or set the following variable via custom-set-variables in the opened buffer by the following command.
; M-x describe-variable RET preview-TeX-style-dir RET
;`(preview-TeX-style-dir ,(concat ".:" (straight--repos-dir "auctex") "latex:"))
(setq preview-TeX-style-dir (concat ".:" (straight--repos-dir "auctex") "latex:"))

(load "auctex.el" nil t t) 
(load "preview-latex.el" nil t t)

(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq-default TeX-master nil)

(use-package eglot
  :hook 
  (
  (TeX-mode . eglot-ensure)
  )
  :config
(add-to-list 'eglot-server-programs
             '((latex-mode tex-mode context-mode texinfo-mode bibtex-mode) . (
             "texlab"
             ))))

(use-package company
  :custom
  (company-idle-delay 0)
  (company-minimum-prefix-length 1)
  (company-tooltip-align-annotations t)
  (company-dabbrev-downcase nil)
  (company-dabbrev-other-buffers t) ; search buffers with the same major mode
  :bind
  ( :map company-active-map
    ("RET" . nil)
    ([return] . nil)
    ("C-w" . nil)
    ("TAB" . company-complete-selection)
    ("<tab>" . company-complete-selection)
    ("C-s" . company-complete-selection)  ; Mostly to use during yasnippet expansion
    ("C-n" . company-select-next)
    ("C-p" . company-select-previous))
  :hook
  ;(dashboard-after-initialize . global-company-mode)
  ; https://emacs.stackexchange.com/questions/39256/how-to-avail-of-hook-using-use-package
  (after-init . global-company-mode)
  :config
  (add-to-list 'company-begin-commands 'backward-delete-char-untabify))

(use-package company-wordfreq
  :straight (:host github :repo "johannes-mueller/company-wordfreq.el")
  :commands my/company-wordfreq-mode
  :custom
  ; This correspoding to the dictionary file:
  ;~/.emacs.d/wordfreq-dicts/english.txt

  ;$ curl -x socks5://127.0.0.1:18888 -O https://raw.githubusercontent.com/hermitdave/FrequencyWords/master/content/2018/en/en_full.txt
  ;$ awk '{print $1}' en_full.txt > my-en_full.txt
  ;$ diff my-en_full.txt english.txt

  (ispell-local-dictionary "english")
  ;(ispell-local-dictionary "my-en_full")
  :config
  (define-minor-mode my/company-wordfreq-mode
    "Suggest words by frequency."
    :global nil
    (if my/company-wordfreq-mode
        (progn
          (company-mode)  ;; <---- ACTIVATE COMPANY MODE
          (setq-local company-backends-backup company-backends)
          (setq-local company-transformers-backup company-transformers)
          (setq-local company-backends '(company-wordfreq))
          (setq-local company-transformers nil))
      (setq-local company-backends company-backends-backup)
      (setq-local company-transformers company-transformers-backup)))) 

;; Show YASnippet snippets in company
(use-package yasnippet)
(defun my/company-backend-with-yas (backend)
    "Add ':with company-yasnippet' to the given company backend."
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend)
                  backend
                (list backend))
              '(:with company-yasnippet))))

  (defun my/company-smart-snippets (fn command &optional arg &rest _)
    "Do not show yasnippet candidates after dot."
    ;;Source:
    ;;https://www.reddit.com/r/emacs/comments/7dnbxl/how_to_temporally_filter_companymode_candidates/
    (unless (when (and (equal command 'prefix) (> (point) 0))
              (let* ((prefix (company-grab-symbol))
                     (point-before-prefix (if (> (- (point) (length prefix) 1) 0)
                                              (- (point) (length prefix) 1)
                                            1))
                     (char (buffer-substring-no-properties point-before-prefix (1+ point-before-prefix))))
                (string= char ".")))
      (funcall fn command arg)))

  ;; TODO: maybe show snippets at first?
  (defun my/company-enable-snippets ()
    "Enable snippet suggestions in company by adding ':with
company-yasnippet' to all company backends."
    (interactive)
    (setq company-backends (mapcar 'my/company-backend-with-yas company-backends))
    (advice-add 'company-yasnippet :around 'my/company-smart-snippets))

;(my/company-enable-snippets)  ;; <------- ACTIVATE SNIPPET COMPLETION
;(add-hook 'my/company-wordfreq-mode-hook 'my/company-enable-snippets)

;https://stackoverflow.com/questions/25778644/add-several-functions-as-hooks-in-emacs
;https://github.com/KaratasFurkan/.emacs.d/issues/1#issuecomment-864574670
(dolist (fn '(my/company-enable-snippets
              my/company-wordfreq-mode))
  (add-hook 'latex-mode-hook fn))

When I open some tex document, only the latex command completion are activated. If I run M-x my/company-wordfreq-mode, then company-wordfreq will be activated, but latex command completion will be disabled, as shown below:

image

Obviously, when I type latex commands, I don't want to have the normal human writing suggestions supplied by company-wordfreq, and vice versa.

How to solve this problem?

Regards, HY

hongyi-zhao commented 3 years ago

Another thing I want to know is whether company supplies fuzzy match based completion for all above methods.