From: Dante Catalfamo Subject: Re: electric-pair-mode for mg [was Re: mg: grep buffer improvements] To: Omar Polo Cc: tech@openbsd.org Date: Tue, 04 Jun 2024 23:08:20 -0400 That's really cool, thanks for sharing! I don't have much experience with C and I've been exploring how mg works and using it as a jumping off point for sending patches to OpenBSD. I've been using the system for years and mg has always been a fascinating tool to me since Emacs users generally don't get a good option OOB on any other OS. I'll try to keep improving the quality of my patches and submitting fixes if I come across anything that looks like it needs work. Best, Dante On Tue, 2024-06-04 at 18:47 +0200, Omar Polo wrote: > sorry for the slighly OT, but thought this might interest you :) > (and maybe a few other readers) > > many moons ago I wrote this to emulate GNU Emacs' electric-pair-mode, > i.e. to automatically close pairs.  It's not in a state where it can > be > committed, some keybindings conflict with other keymaps and care > needs > to be used to enable electric-pair-mode *after* c-mode, epnewline is > also conflicting with auto-indent-mode and/or c-mode IIRC.  I'd need > to > understand better how keybindings are handled and what can be done > for > conflicting ones and/or to "fallback".  Still, it's something i keep > in > my tree and use always :) > > > diff d784f91cfcb7086a5e38d59ae74fcadbcf1bb1af > dfb48153daf1efa4cef5b96317016965cbee0176 > commit - d784f91cfcb7086a5e38d59ae74fcadbcf1bb1af > commit + dfb48153daf1efa4cef5b96317016965cbee0176 > blob - fc3899772ec9892528274d0f22f438cac31c24b0 > blob + aff6a4016f5ca65616667786c9cc8288f2389ab4 > --- usr.bin/mg/Makefile > +++ usr.bin/mg/Makefile > @@ -22,7 +22,7 @@ SRCS= autoexec.c basic.c bell.c buffer.c cinfo.c > dir.c >  # >  # More or less standalone extensions. >  # > -SRCS+= cmode.c cscope.c dired.c grep.c interpreter.c tags.c > +SRCS+= cmode.c cscope.c dired.c grep.c interpreter.c tags.c > electric.c >   >  # >  # -DMGLOG source file. > blob - 403635036ceea9bcb7ca995a0c55b5ebe11e7372 > blob + a1d50b94798aed6b5e2c2797fcb18a054cc096e7 > --- usr.bin/mg/def.h > +++ usr.bin/mg/def.h > @@ -20,7 +20,7 @@ typedef int (*PF)(int, int); /* generally > useful type >  #define NFILEN 1024 /* Length, file name. */ >  #define NBUFN NFILEN /* Length, buffer > name. */ >  #define NLINE 256 /* Length, line. */ > -#define PBMODES 4 /* modes per buffer */ > +#define PBMODES 8 /* modes per buffer */ >  #define NPAT 80 /* Length, pattern. */ >  #define HUGE 1000 /* A rather large number. */ >  #define NSRCH 128 /* Undoable search commands. */ > @@ -722,6 +722,14 @@ int cc_tab(int, int); >  int cc_indent(int, int); >  int cc_lfindent(int, int); >   > +/* electric.c X */ > +int epmode(int, int); > +int epinsert(int, int); > +int epskip(int, int); > +int epbdel(int, int); > +int epfdel(int, int); > +int epnewline(int, int); > + >  /* grep.c X */ >  int next_error(int, int); >  int globalwdtoggle(int, int); > blob - /dev/null > blob + 1a5e05464b74cbd477686bb4d4828bfa3a8c49ec (mode 644) > --- /dev/null > +++ usr.bin/mg/electric.c > @@ -0,0 +1,280 @@ > +/* $OpenBSD$ */ > +/* > + * This file is in the public domain. > + * > + * Author: Omar Polo > + */ > + > +#include > +#include > +#include > + > +#include "def.h" > +#include "funmap.h" > +#include "kbd.h" > +#include "key.h" > + > +/* Pull in from modes.c */ > +extern int changemode(int, int, char *); > + > +int pairp(int, int); > +int epclosep(void); > + > +/* Keymaps */ > + > +static PF ele_cmap[] = { > + epfdel, /* ^D */ > + rescan, /* ^E */ > + rescan, /* ^F */ > + rescan, /* ^G */ > + rescan, /* ^H */ > + rescan, /* ^I */ > + rescan, /* ^J */ > + rescan, /* ^K */ > + rescan, /* ^L */ > + rescan, /* ^M */ > + //epnewline, /* ^M */ > +}; > + > +static PF ele_quote[] = { > + epinsert, /* " */ > +}; > + > +static PF ele_apostrophe[] = { > + epinsert, /* ' */ > +}; > + > +static PF ele_paren[] = { > + epinsert, /* ( */ > + epskip, /* ) */ > +}; > + > +static PF ele_bracket[] = { > + epinsert, /* [ */ > + rescan, /* \ */ > + epskip, /* ] */ > +}; > + > +static PF ele_backtick[] = { > + epinsert, /* ` */ > +}; > + > +static PF ele_high[] = { > + epinsert, /* { */ > + rescan, /* | */ > + epskip, /* } */ > + rescan, /* ~ */ > + epbdel, /* DEL */ > +}; > + > +static struct KEYMAPE (7) epmodemap = { > + 7, > + 7, > + rescan, > + { > + { CCHR('D'), CCHR('M'), ele_cmap, NULL }, > + { '"', '"', ele_quote, NULL }, > + { '\'', '\'', ele_apostrophe, NULL }, > + { '(', ')', ele_paren, NULL }, > + { '[', ']', ele_bracket, NULL }, > + { '`', '`', ele_backtick, NULL }, > + { '{', CCHR('?'), ele_high, NULL }, > + } > +}; > + > +/* Function, Mode hooks */ > + > +void > +epmode_init(void) > +{ > + funmap_add(epmode, "electric-pair-mode", 0); > + maps_add((KEYMAP *)&epmodemap, "ep"); > +} > + > +/* > + * Enable/toggle electric-pair-mode. > + */ > +int > +epmode(int f, int n) > +{ > + return (changemode(f, n, "ep")); > +} > + > +/* Do o and c form a pair? */ > +int > +pairp(int o, int c) > +{ > + switch (o) { > + case '"': > + case '\'': > + case '`': > + return (c == o); > + case '(': > + return (c == ')'); > + case '[': > + return (c == ']'); > + case '{': > + return (c == '}'); > + } > + return (FALSE); > +} > + > +/* Can we skip over the character? */ > +int > +epclosep(void) > +{ > + int c; > + > + c = key.k_chars[key.k_count - 1]; > + if (curwp->w_doto < llength(curwp->w_dotp)) > + return (c == lgetc(curwp->w_dotp, curwp->w_doto)); > + return (FALSE); > +} > + > +/* > + * Handle pair character - selfinsert then selfinsert. > + */ > +int > +epinsert(int f, int n) > +{ > + int s, c; > + > + if (n < 0) > + return (FALSE); > + > + if (n == 0) > + return (TRUE); > + > + if (n == 1 && epclosep()) > + return (forwchar(FFRAND, 1)); > + > + c = key.k_chars[key.k_count - 1]; > + if ((s = selfinsert(FFRAND, n)) != TRUE) > + return (s); > + > + switch (c) { > + case '"': > + case '\'': > + case '`': > + s = linsert(n, c); > + break; > + case '(': > + s = linsert(n, ')'); > + break; > + case '[': > + s = linsert(n, ']'); > + break; > + case '{': > + s = linsert(n, '}'); > + break; > + } > + > + if (s != TRUE) > + return (s); > + > + return (backchar(FFRAND, n)); > +} > + > +/* > + * Do forwchar if trying to insert a character equal to the next > one. > + */ > +int > +epskip(int f, int n) > +{ > + if (n == 1 && epclosep()) > + return (forwchar(FFRAND, 1)); > + return (selfinsert(f, n)); > +} > + > +/* > + * Handle deletion of a character trying to keep pairs balanced. > + */ > +int > +epbdel(int f, int n) > +{ > + int s, o, c; > + > + if (n < 0) > + return (epfdel(f | FFRAND, -n)); > + > + while (n--) { > + o = '\0'; > + c = '\0'; > + > + /* peek at the character to delete */ > + if (curwp->w_doto > 0) > + o = lgetc(curwp->w_dotp, curwp->w_doto - 1); > + > + /* do the delete */ > + if ((s = backdel(FFRAND, 1)) != TRUE) > + return (s); > + > + /* peek at the next character */ > + if (curwp->w_doto < llength(curwp->w_dotp)) > + c = lgetc(curwp->w_dotp, curwp->w_doto); > + > + if (!pairp(o, c)) > + continue; > + if ((s = forwdel(FFRAND, 1)) != TRUE) > + return (s); > + } > + > + return (TRUE); > +} > + > +/* > + * Handle deletion of a character trying to keep pairs balanced. > + */ > +int > +epfdel(int f, int n) > +{ > + int s, o, c; > + > + if (n < 0) > + return (epbdel(f | FFRAND, -n)); > + > + while (n--) { > + o = '\0'; > + c = '\0'; > + > + /* peek at the character to delete */ > + if (curwp->w_doto < llength(curwp->w_dotp)) > + c = lgetc(curwp->w_dotp, curwp->w_doto); > + > + /* do the delete */ > + if ((s = forwdel(FFRAND, 1)) != TRUE) > + return (s); > + > + /* peek at the prev character */ > + if (curwp->w_doto > 0) > + o = lgetc(curwp->w_dotp, curwp->w_doto - 1); > + > + if (!pairp(o, c)) > + continue; > + if ((s = backdel(FFRAND, 1)) != TRUE) > + return (s); > + } > + > + return (TRUE); > +} > + > +int > +epnewline(int f, int n) > +{ > + int s, o, c; > + > + if (n != 1 || curwp->w_doto == 0 || > +     curwp->w_doto == llength(curwp->w_dotp)) > + return (lfindent(f, n)); > + > + o = lgetc(curwp->w_dotp, curwp->w_doto - 1); > + c = lgetc(curwp->w_dotp, curwp->w_doto); > + if (!pairp(o, c)) > + return (lfindent(f, n)); > + > + if ((s = lfindent(FFRAND, 2)) != TRUE || > +     (s = backline(FFRAND, 1)) != TRUE || > +     (s = gotoeol(FFRAND, 1)) != TRUE) > + return (s); > + return (TRUE); > +} > blob - 56ef7d9a0429e15ac6af9645781f9e7f2042f16d > blob + 20f8ab03d4fb500e2944327bfeea1ec1f34adc5b > --- usr.bin/mg/main.c > +++ usr.bin/mg/main.c > @@ -137,10 +137,12 @@ main(int argc, char **argv) >   extern void grep_init(void); >   extern void cmode_init(void); >   extern void dired_init(void); > + extern void epmode_init(void); >   >   dired_init(); >   grep_init(); >   cmode_init(); > + epmode_init(); >   } >   >   if (init_fcn_name && >