Index | Thread | Search

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

Download raw body.

Thread
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 <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 &&
>