3

I am trying to find an easy and efficient way to modify the behaviour of forward-sexp to handle balanced pairs of characters not normally supported by it, such as balanced < and > and balanced << and >>.

My specific case is to enhance the support of Erlang but I believe it could apply to a lot of scenarios.

The implementation of forward-sexp allows the use of a forward-sexp-function which means I could implement such a function. I am also aware of the SMIE library and will look into it.

However I was hoping to find a variable that the C-implemented scan-sexp could use to define the matching pair but have not succeeded so far. It would seem the easiest and most efficient way of implementing such handling of balanced pair would be done there.

Is there a variable one can use to augment or modify the behaviour of scan-sexp?

Drew
  • 75,699
  • 9
  • 109
  • 225
PRouleau
  • 744
  • 3
  • 10
  • Did you have a look at `electric-pair-text-pairs`? – aadcg Sep 27 '21 at 16:18
  • Have you tried [Erlang mode](https://github.com/erlang/otp)? I don't know anything, either about Erlang or Erlang mode, but that's where I would start if I had to use it. – NickD Sep 27 '21 at 16:30
  • @aadcg I did not know `electric-pair-text-pairs`. I will look into it. Thanks – PRouleau Sep 27 '21 at 16:53
  • @NickD . Yes I am using erlang-mode from erlang.el. That's what I'm trying to enhance. – PRouleau Sep 27 '21 at 17:09
  • I tried adding the '(?< . ?>) pair to electric-pair-pairs and to electric-pair-text-pairs. What it does is add the automatic insertion of the matched pair character when the minor mode electric-pair-mode is activated. It does not seem to help navigation though. Although this electric-pair-mode is useful, I find that the smartparens-mode has more features. For Erlang I am using erlang.el and smartparens, but some navigation and matching are sill lacking, therefore my question. Here's what [my code supports](https://raw.githubusercontent.com/pierre-rouleau/pel/master/doc/pdf/pl-erlang.pdf). – PRouleau Sep 27 '21 at 17:17
  • @PRouleau, my hope was that adding the pairs would perhaps influence the sexp-wise navigation. Somehow my intuition was right, but smth fishy is happening. Follow these steps. Take a look at `insert-pair-alist`. Now open a buffer in `org-mode` and turn on `electric-pair-mode`. It surrounds regions with the pairs found in `insert-pair-alist` (including "< >") and you can even navigate them with `forward-sexp`!!! If I try the same with, say python-mode, it doesn't work! – aadcg Sep 27 '21 at 18:16
  • I used to use smartparens, but there's too much functionality for me. I like the built-in packages, it just lacks consistency. I might bring this subject to the Emacs mailing list (if no one does it first). – aadcg Sep 27 '21 at 18:20
  • @aadcg I suspect the behaviour of navigation to be influenced by the syntax table used by the current major mode. I'm reading on char-tables and syntax table now. BTW, I agree with your comment on smartparens and I'm trying to solve issues I had with it in Erlang as well. I disable it for looking into navigation by `forward-sexp` only. I' d like to concentrate on the implementation of `forward-sexp` because I think it might already support what I need but don't know how to get to it. – PRouleau Sep 27 '21 at 18:30
  • Oh, I think you're on the right track regarding the syntax tables. Those are indeed per-mode afaik. When you fix this, please write an answer. – aadcg Sep 27 '21 at 18:36
  • Perhaps, when looking at the syntax table of erlang-mode, the syntax of `(`, `{` and `[` are all open delimiters of their matching character. But `<` is just a punctuation. Il tried changing it to be an open delimiter for `>` with: `(modify-syntax-entry ?< "(>" erlang-mode-syntax-table)` and `(modify-syntax-entry ?> "(<" erlang-mode-syntax-table)`. I can see the change take effect in the table but `forward-sexp` behaviour does not change. – PRouleau Sep 27 '21 at 19:08
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/130032/discussion-between-aadcg-and-prouleau). – aadcg Sep 27 '21 at 19:45

1 Answers1

2

I have been able to get forward-sexp to recognize the < and > pair by modifying the syntax table the erlang-mode uses:

The erlang.el file has a function that sets the syntax table when the erlang-mode takes effect. It does the following:

(modify-syntax-entry ?< "." table)
(modify-syntax-entry ?> "." table)

but it should be this instead:

(modify-syntax-entry ?< "(>" table)
(modify-syntax-entry ?> ")<" table)

Since I can't modify erlang.el, a hook has to be used to modify the erlang.el syntax table variable: erlang-mode-syntax-table somewhere in initialization code I used something like this:

 (with-eval-after-load 'erlang

  (defvar erlang-mode-syntax-table) ; prevent byte compiler warning

  (add-hook 'erlang-mode-hook 
            (lambda () 
              "Add < > pair matching."
               (modify-syntax-entry ?< "(>" erlang-mode-syntax-table)
               (modify-syntax-entry ?> ")<" erlang-mode-syntax-table)))

The defvar is there to prevent byte-compiler warnings only. The with-eval-after-load 'erlang delays execution of the code after erlang.el has loaded to ensure the hook is not over-written.

Alternative (but more complex)

An alternative, which also works but requires much more code would have been to write a function my-erlang-forward-sexp that handles the pairing it self and behaves as a replacement for forward-sexp, and then place the following inside the hook function instead:

(setq-local forward-sexp-function (function my-erlang-forward-sexp))

It would set forward-sexp-function to my-erlang-forward-sexp only in buffers using the erlang-mode and would not modify the behaviour of other major modes.

However writing that function is more complex. Modifying the syntax table is much simpler to do and takes less code.

PRouleau
  • 744
  • 3
  • 10
  • 1
    Thanks to @aadcg for suggesting I ask on gnu emacs mailing list and to [Thibaut Verron](https://lists.gnu.org/archive/html/help-gnu-emacs/2021-09/msg00471.html) who spotted a typo in my syntax table change that prevented the change to work. – PRouleau Sep 28 '21 at 12:14
  • `sets-local` is a typo, right? I presume the syntax-table mod works with `<<` and `>>` as well? If `erlang-mode` sets the hook to `nil` or some non-nil value, then depending on when it is loaded, you might find your customization to be AWOL. It may be safer to use `with-eval-after-load` instead of the `defvar`. – NickD Sep 28 '21 at 17:20
  • @NickD, Thanks: I fixed the typo (caused by me not looking at what was autocorrected...): it should have read `setq-local`. And yes the syntax table mods make ``<<`` match the ``>>`` pair. It's also working with **er/expand-region**. As for your last comment I'm not sure I understand. – PRouleau Sep 28 '21 at 17:58
  • If you don't have a problem with your current setup, you can ignore my last comment. If you do encounter a problem (you check the hook and it does not contain your function), then we can worry about it :-) – NickD Sep 28 '21 at 18:13
  • @NickD, what would be the sequence where the hook would not contain the function I added to go in the hook? The function is added to the hook before the erlang-mode is defined, which is when erlang.el is loaded. I know I had a similar problem in the past but can't remember what it was. – PRouleau Sep 28 '21 at 18:24
  • @NickD Were you referring to the fact that the major-mode run the hook after processing a file's local variable? To be extra safe, I sometimes use a 2-step mechanism using the hack-local-hook-variable as the intermediate step. The code worked without it. – PRouleau Sep 28 '21 at 18:44
  • No, I was referring to what happens if you try to use a variable before it is defined: `erlang-mode-hook` is a void-variable until `erlang-mode.el[c]` is loaded, so trying to add to it in your init file might fail. The usual method to deal with that is to defer the manipulation until after the file that defines the variable is loaded: that's what `with-eval-after-load` does. – NickD Sep 28 '21 at 20:21
  • @NickD Ah, thanks. That makes sense. Its not the case for erlang.el. (featurep 'erlang) return nil just after startup, even though erlang-mode-hook is non void. My definition is not over-written by erlang.el code, but you mean one day it could be. Correct? – PRouleau Sep 28 '21 at 20:53
  • Something like that :-) As I said, if there is no problem, no need to worry about it. If there *was* a problem, you would have discovered it after restarting emacs. – NickD Sep 28 '21 at 21:58
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/130067/discussion-between-prouleau-and-nickd). – PRouleau Sep 28 '21 at 21:58