From: Omar Polo Subject: electric-pair-mode for mg [was Re: mg: grep buffer improvements] To: Dante Catalfamo Cc: tech@openbsd.org Date: Tue, 04 Jun 2024 18:47:13 +0200 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 &&