Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
electric-pair-mode for mg [was Re: mg: grep buffer improvements]
To:
Dante Catalfamo <dante@lambda.cx>
Cc:
tech@openbsd.org
Date:
Tue, 04 Jun 2024 18:47:13 +0200

Download raw body.

Thread
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 <op@openbsd.org>
+ */
+
+#include <sys/queue.h>
+#include <signal.h>
+#include <stdio.h>
+
+#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 &&