0

Following up on Different abbrevs for each major mode, which covers the first step that I found from here. And now, trying to cover to the next step.

In summary,

  • the first step, i.e., Different abbrevs for each major mode, solves the problem of allowing abbreviations with leading "`" for each different major mode
  • the next step, is to allow moving cursor into the proper position after expansion, which explained clearly in the above wiki

Please take a look at the code change at https://www.diffchecker.com/7TPMxkzU. It has two sides, which are two cases that I tried for the next step. They are basically all of my config, apart from load-path setting. On the left, everything is working; whereas on the right, abbreviations with leading "`" are not working any more. In details,

      (define-abbrev-table 'python-mode-abbrev-table    
    '(  
      ("fo" "bar")  
      ("`foo" "bar")    
      ("`p" "print()" ahf)  
      ;;    
      ))

The first two definitions:

      ("fo" "bar")  
      ("`foo" "bar")    
  • with the config file on the left side, both are working.
  • with the config file on the right side, only 1st working. 2nd is no longer working -- when I type "`foo" and press space, nothing happens.

The difference is the following code are being added:

;; == adapted from http://ergoemacs.org/emacs/elisp_abbrev.html

;;  the “ahf” stand for abbrev hook function.
(defun ahf ()
  "Abbrev hook function, used for `define-abbrev'.
prevent inserting the char that triggered expansion."
  (interactive)
  t)

(put 'ahf 'no-self-insert t)

(setq abbrev-expand-function 'global-expand-abbrev)
;; (setq abbrev-expand-function 'abbrev--default-expand)

(defun global-expand-abbrev ()
  "Function for value of `abbrev-expand-function'.
Expand the symbol before cursor.
Returns the abbrev symbol if there's a expansion, else nil."
  (interactive)
  (let ( $p1 $p2
             $abrStr
             $abrSymbol
             )

    (save-excursion
      (forward-symbol -1)
      (setq $p1 (point))
      (forward-symbol 1)
      (setq $p2 (point)))

    (setq $abrStr (buffer-substring-no-properties $p1 $p2))
    (setq $abrSymbol (abbrev-symbol $abrStr))
    (if $abrSymbol
        (progn
          (abbrev-insert $abrSymbol $abrStr $p1 $p2 )
          (global-abbrev-position-cursor $p1)
          $abrSymbol)
      nil)))

(defun global-abbrev-position-cursor (&optional @pos)
  "Move cursor (from @POS) back to ▮ if exist, else put at end.
Return true if found, else false."
  (interactive)
  (let (($found-p (search-backward "▮" (if @pos @pos (max (point-min) (- (point) 100))) t )))
    (when $found-p (delete-char 1))
    $found-p
    ))

I've made the above code work for abbreviations without leading "`", but now, when the two are put together, abbreviations with leading "`", and the above code, things are not working any more.

I don't know how to fix it as I don't know elisp except copying from working code.
Somebody help please.

PS. while you're at it, I'm also getting the following warning for above code:

  Argument `@pos' should appear (as @POS) in the doc string (emacs-lisp-checkdoc)

which I have no idea how to fix either.

UPDATE:

I've documented my changes at https://www.diffchecker.com/BjiDYyJP, and set it to never expire. But just in case anything happens, here are the code again:

Before
;; https://emacs.stackexchange.com/questions/66410/different-abbrevs-for-each-major-mode

(defvar my-abbrev-regexp
  (rx (or bol (not (any "`" wordchar)))
      (group (one-or-more (any "`" wordchar)))
      (zero-or-more (not (any "`" wordchar))))
  "Use as :regexp in abbrev tables to make \\=` a valid abbrev char.

If `words-include-escapes' is used then this regexp can fail.
Refer to the elisp comments in `abbrev--before-point' for details.")

(abbrev-table-put emacs-lisp-mode-abbrev-table :regexp my-abbrev-regexp)
(define-abbrev emacs-lisp-mode-abbrev-table "`foo" "bar")
(add-hook 'emacs-lisp-mode-hook #'abbrev-mode)

;; use it with a mode which is not loaded by default
(with-eval-after-load "sh-script"
  (abbrev-table-put sh-mode-abbrev-table :regexp my-abbrev-regexp)
  (define-abbrev sh-mode-abbrev-table "`foo" "bar")
  (add-hook 'sh-mode-hook #'abbrev-mode))

(progn
  ;; python
  (when (boundp 'python-mode-abbrev-table)
    (clear-abbrev-table python-mode-abbrev-table))

  (define-abbrev-table 'python-mode-abbrev-table
    '(

      ("fo" "bar")
      ("p" "print(▮)" ahf)
      ("`foo" "bar")
      ("`p" "print()" ahf)
      ;;

      ))
  (abbrev-table-put python-mode-abbrev-table :regexp my-abbrev-regexp)
  (add-hook 'python-mode-hook #'abbrev-mode)
  )

;; == adapted from http://ergoemacs.org/emacs/elisp_abbrev.html

;;  the “ahf” stand for abbrev hook function.
(defun ahf ()
  "Abbrev hook function, used for `define-abbrev'.
prevent inserting the char that triggered expansion."
  (interactive)
  t)

(put 'ahf 'no-self-insert t)

(setq abbrev-expand-function 'global-expand-abbrev)
;; (setq abbrev-expand-function 'abbrev--default-expand)

(defun global-expand-abbrev ()
  "Function for value of `abbrev-expand-function'.
Expand the symbol before cursor.
Returns the abbrev symbol if there's a expansion, else nil."
  (interactive)
  (let ( $p1 $p2
             $abrStr
             $abrSymbol
             )

    (save-excursion
      (forward-symbol -1)
      (setq $p1 (point))
      (forward-symbol 1)
      (setq $p2 (point)))

    (setq $abrStr (buffer-substring-no-properties $p1 $p2))
    (setq $abrSymbol (abbrev-symbol $abrStr))
    (if $abrSymbol
        (progn
          (abbrev-insert $abrSymbol $abrStr $p1 $p2 )
          (global-abbrev-position-cursor $p1)
          $abrSymbol)
      nil)))

(defun global-abbrev-position-cursor (&optional @pos)
  "Move cursor (from @POS) back to ▮ if exist, else put at end.
Return true if found, else false."
  (interactive)
  (let (($found-p (search-backward "▮" (if @pos @pos (max (point-min) (- (point) 100))) t )))
    (when $found-p (delete-char 1))
    $found-p
    ))

(setq save-abbrevs nil)
After
;; https://emacs.stackexchange.com/questions/66410/different-abbrevs-for-each-major-mode

(defvar my-abbrev-regexp
  "\\(`[0-9A-Za-z._-]+\\)"
  "Use as :regexp in abbrev tables to make \\=` a valid abbrev char.

If making \\=` optional (suffix it with ?), `re-search-backward' will
not be able to be that aggressive to match to it.  Thus, making
the leading \\=` mandatory.

If `words-include-escapes' is used then this regexp can fail.
Refer to the elisp comments in `abbrev--before-point' for details.")

(abbrev-table-put emacs-lisp-mode-abbrev-table :regexp my-abbrev-regexp)
(define-abbrev emacs-lisp-mode-abbrev-table "`foo" "bar")
(add-hook 'emacs-lisp-mode-hook #'abbrev-mode)

;; use it with a mode which is not loaded by default
(with-eval-after-load "sh-script"
  (abbrev-table-put sh-mode-abbrev-table :regexp my-abbrev-regexp)
  (define-abbrev sh-mode-abbrev-table "`foo" "bar")
  (add-hook 'sh-mode-hook #'abbrev-mode))

(progn
  ;; python
  (when (boundp 'python-mode-abbrev-table)
    (clear-abbrev-table python-mode-abbrev-table))

  (define-abbrev-table 'python-mode-abbrev-table
    '(

      ("`foo" "bar")
      ("`p" "print(▮)" ahf)
      ;;

      ))
  (abbrev-table-put python-mode-abbrev-table :regexp my-abbrev-regexp)
  (add-hook 'python-mode-hook #'abbrev-mode)
  )

;; == adapted from http://ergoemacs.org/emacs/elisp_abbrev.html

;;  the “ahf” stand for abbrev hook function.
(defun ahf ()
  "Abbrev hook function, used for `define-abbrev'.
prevent inserting the char that triggered expansion."
  (interactive)
  t)

(put 'ahf 'no-self-insert t)

(setq abbrev-expand-function 'global-expand-abbrev)
;; (setq abbrev-expand-function 'abbrev--default-expand)

(defun global-expand-abbrev ()
  "Function for value of `abbrev-expand-function'.
Expand the symbol before cursor.
Returns the abbrev symbol if there's a expansion, else nil."
  (interactive)
  (let ( $p1 $p2
             $abrStr
             $abrSymbol
             )

    (save-excursion
      ;; (forward-symbol -1)
      (re-search-backward my-abbrev-regexp)
      (setq $p1 (point))
      (forward-symbol 1)
      (setq $p2 (point)))

    (setq $abrStr (buffer-substring-no-properties $p1 $p2))
    (setq $abrSymbol (abbrev-symbol $abrStr))
    (if $abrSymbol
        (progn
          (abbrev-insert $abrSymbol $abrStr $p1 $p2 )
          (global-abbrev-position-cursor $p1)
          $abrSymbol)
      nil)))

(defun global-abbrev-position-cursor (&optional @pos)
  "Move cursor (from @POS) back to ▮ if exist, else put at end.
Return true if found, else false."
  (interactive)
  (let (($found-p (search-backward "▮" (if @pos @pos (max (point-min) (- (point) 100))) t )))
    (when $found-p (delete-char 1))
    $found-p
    ))

(setq save-abbrevs nil)

NB my above solution is different from phils answer, which accepted as the correct answer, and it'll remain that way.

6
  • This question is too broad. It contains multiple questions and is not focused. It essentially asks readers to go look at a mass of code and debug it for you. Please repost the question as a narrow, specific how-to. Thx.
    – Drew
    Jul 1, 2021 at 16:39
  • This is the best I can do. If you compare my code with that from the wiki originally, you can see that I've done as much simplification as I could. And if you have checked out the code diff that I posted online, you can see that I've validated that ahf function is not the source of the problem. I.e., the source of the problem is only the added two new functions, global-expand-abbrev, and global-abbrev-position-cursor, which I had validated that they both work for case without the leading "`". If you don't want to answer it, please leave it for somebody else. thanks.
    – xpt
    Jul 1, 2021 at 16:46
  • Moreover, it is not that I'm asking someone to debug a mass of code for me only, it is about a code from the emacs official wiki, that is supposed to be helpful for everybody, including those who don't know elisp, like me. Furthermore, both ideas are in that wiki, using a leading character and position-cursor. I'm only trying to put 2 and 2 together, connecting the missing dots.
    – xpt
    Jul 1, 2021 at 16:57
  • I don't understand what you're trying to achieve. I realise that you don't entirely understand the code that you've copied, but you haven't explained what you even want it to do.
    – phils
    Jul 1, 2021 at 22:33
  • You need to provide examples and details: What config are you using? What works? What doesn't work? In which mode(s)? What is the intended result and what was the actual result? Please show these things so that others can see what you're trying to do, verify it, and test whether a solution works.
    – phils
    Jul 2, 2021 at 2:39

1 Answer 1

2

The problem is probably the use of forward-symbol if ` is not a symbol-constituent character (much as the problem in the prior question was that this character was not word-constituent).

A character is symbol-constituent if it has either word or symbol syntax (i.e. symbol syntax is for the additional chars that aren't already word-constituent).

As before you could give ` word or symbol syntax, but it would have other side-effects, so I'm inclined to again resolve this by replacing syntax-based movement with a regexp-based movement.

Try this change to global-expand-abbrev:

      ;; (forward-symbol -1)
      (re-search-backward my-abbrev-regexp)
      (re-search-forward "^\\|.")

n.b. It ignores the subsequent call to (forward-symbol 1) only on the basis that I know you're only using ` at the very start of the abbrev. If you were using it within an abbrev, then you'd need to account for that too.

4
  • Thanks a lot phils, I got it working with your help. and documented my changes at diffchecker.com/BjiDYyJP.
    – xpt
    Jul 4, 2021 at 15:19
  • I see you changed the regexp in a way which I believe will break abbrevs which do NOT start with ` -- it was written that way for a reason. Also the trailing part of the original regexp was for compatibility with abbrev's own original regexp, so I assume dropping that is also problematic. If my suggestion here wasn't actually working then we can address that, but I'm dubious about what you've done here. Lastly, this "diffchecker" web site might go offline or potentially delete old content... better to include your code in the question.
    – phils
    Jul 5, 2021 at 22:23
  • Yes phils, I made it clear in the comment that for the explained reason, "Thus, making the leading \\=` mandatory.". I didn't meant to say you were wrong, just trying to give another option for everybody, especially those who don't know elisp, like me. Your solution is accepted as the correct answer, and it'll remain that way. I've set the post on diffchecker to never expire, but I'll include the code in my question, as per your request.
    – xpt
    Jul 6, 2021 at 4:03
  • Right, I see. It seems like an unnecessary constraint to me, but so long as you're aware :)
    – phils
    Jul 6, 2021 at 7:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.