From 8eef2dc8294a551009f2ee63b92aa14983b0b811 Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Debian Linux)" <rob@synchro.net>
Date: Thu, 25 Apr 2024 23:00:46 -0700
Subject: [PATCH] Add MNE:<low>[:high][:cmd] @-code for mnemonic string
 attribute control

The "default" mnemonic string attributes are still set in ctrl/attr.cfg,
but now, each mnemonic string (e.g. from text.dat or passed to JS
console.mnemonics()) can include a "MNE:" @-code to over-ride the default
mnemonic string attributes (low, high, and command). If the "high"
attribute isn't specified, it defaults to the same color as the "low"
attribute with the "high intensity" flag flipped. The "cmd" attribute
defaults to whatever was included in the attr.cfg, if not specified.
Technically, the separator between the attributes can be any non-valid
attribute character (e.g. symbol).

Renamed (really old function) attrstr() to strtoattr() since its usage now
more closely resembles other std C strto* functions (has an 'endptr' arg).
---
 src/sbbs3/atcodes.cpp    | 13 ++++++++++++-
 src/sbbs3/getkey.cpp     | 17 +++++++++++------
 src/sbbs3/js_console.cpp |  4 ++--
 src/sbbs3/load_cfg.c     |  2 +-
 src/sbbs3/sbbs.h         |  3 +++
 src/sbbs3/scfglib.h      |  2 +-
 src/sbbs3/scfglib2.c     |  8 +++++++-
 7 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index 086cb2fe82..1760b8b223 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -338,7 +338,7 @@ const char* sbbs_t::atcode(const char* sp, char* str, size_t maxlen, int* pmode,
 		else if(stricmp(sp, "off") == 0)
 			hot_attr = 0;
 		else
-			hot_attr = attrstr(sp);
+			hot_attr = strtoattr(sp, /* endptr: */NULL);
 		return nulstr;
 	}
 	if(strcmp(sp, "CLEAR_HOT") == 0) {
@@ -346,6 +346,17 @@ const char* sbbs_t::atcode(const char* sp, char* str, size_t maxlen, int* pmode,
 		return nulstr;
 	}
 
+	if(strncmp(sp, "MNE:", 4) == 0) {	// Mnemonic attribute control
+		sp += 4;
+		mneattr_low = strtoattr(sp, &tp);
+		mneattr_high = mneattr_low ^ HIGH;
+		if(tp != NULL && *tp != '\0')
+			mneattr_high = strtoattr(tp + 1, &tp);
+		if(tp != NULL && *tp != '\0')
+			mneattr_cmd = strtoattr(tp + 1, NULL);
+		return nulstr;
+	}
+
 	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
 		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
 		if(tp == NULL || *tp == 0)
diff --git a/src/sbbs3/getkey.cpp b/src/sbbs3/getkey.cpp
index fedcccd048..77712fe128 100644
--- a/src/sbbs3/getkey.cpp
+++ b/src/sbbs3/getkey.cpp
@@ -191,12 +191,17 @@ void sbbs_t::mnemonics(const char *instr)
 			bputs(instr);
 			return; 
 		}
-		attr(cfg.color[clr_mnelow]); 
 	}
+
+	mneattr_low = cfg.color[clr_mnelow];
+	mneattr_high = cfg.color[clr_mnehigh];
+	mneattr_cmd = cfg.color[clr_mnecmd];
+
 	char str[256];
 	expand_atcodes(instr, str, sizeof str);
 	l=0L;
 	int term = term_supports();
+	attr(mneattr_low);
 
 	while(str[l]) {
 		if(str[l]=='~' && str[l+1] < ' ') {
@@ -208,28 +213,28 @@ void sbbs_t::mnemonics(const char *instr)
 				outchar('(');
 			l++;
 			if(!ctrl_a_codes)
-				attr(cfg.color[clr_mnehigh]);
+				attr(mneattr_high);
 			add_hotspot(str[l], /* hungry: */true);
 			outchar(str[l]);
 			l++;
 			if(!(term&(ANSI|PETSCII)))
 				outchar(')');
 			if(!ctrl_a_codes)
-				attr(cfg.color[clr_mnelow]); 
+				attr(mneattr_low);
 		}
 		else if(str[l]=='`' && str[l+1]!=0) {
 			if(!(term&(ANSI|PETSCII)))
 				outchar('[');
 			l++;
 			if(!ctrl_a_codes)
-				attr(cfg.color[clr_mnehigh]);
+				attr(mneattr_high);
 			add_hotspot(str[l], /* hungry: */false);
 			outchar(str[l]);
 			l++;
 			if(!(term&(ANSI|PETSCII)))
 				outchar(']');
 			if(!ctrl_a_codes)
-				attr(cfg.color[clr_mnelow]); 
+				attr(mneattr_low);
 		}
 		else {
 			if(str[l]==CTRL_A && str[l+1]!=0) {
@@ -243,7 +248,7 @@ void sbbs_t::mnemonics(const char *instr)
 		} 
 	}
 	if(!ctrl_a_codes)
-		attr(cfg.color[clr_mnecmd]);
+		attr(mneattr_cmd);
 }
 
 /****************************************************************************/
diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp
index 1e1c6a50cd..8fc46b1864 100644
--- a/src/sbbs3/js_console.cpp
+++ b/src/sbbs3/js_console.cpp
@@ -293,7 +293,7 @@ static JSBool js_console_set(JSContext *cx, JSObject *obj, jsid id, JSBool stric
 				JSVALUE_TO_MSTRING(cx, *vp, sval, NULL);
 				if(sval==NULL)
 					break;
-				val=attrstr(sval);
+				val=strtoattr(sval, /* endptr: */NULL);
 				free(sval);
 			}
 			rc=JS_SUSPENDREQUEST(cx);
@@ -1112,7 +1112,7 @@ js_set_attr(JSContext* cx, sbbs_t* sbbs, jsval val)
 		JSVALUE_TO_MSTRING(cx, val, as, NULL);
 		if(as==NULL)
 			return JS_FALSE;
-		attr=attrstr(as);
+		attr=strtoattr(as, /* endptr: */NULL);
 		free(as);
 	}
 	else {
diff --git a/src/sbbs3/load_cfg.c b/src/sbbs3/load_cfg.c
index 5c196088c5..f6b4c545d6 100644
--- a/src/sbbs3/load_cfg.c
+++ b/src/sbbs3/load_cfg.c
@@ -440,7 +440,7 @@ bool read_attr_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 				break;
 			cfg->color=clr;
 		}
-		cfg->color[cfg->total_colors]=attrstr(str); 
+		cfg->color[cfg->total_colors]=strtoattr(str, /* endptr: */NULL); 
 	}
 	fclose(instream);
 	if(cfg->total_colors<MIN_COLORS)
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 6741bcca33..983f8eedaa 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -624,6 +624,9 @@ public:
 	uint	curatr = LIGHTGRAY;	/* Current Text Attributes Always */
 	uint	attr_stack[64]{};	/* Saved attributes (stack) */
 	int 	attr_sp = 0;	/* Attribute stack pointer */
+	uint	mneattr_low = LIGHTGRAY;
+	uint	mneattr_high = LIGHTGRAY;
+	uint	mneattr_cmd = LIGHTGRAY;
 	int 	lncntr = 0; 	/* Line Counter - for PAUSE */
 	bool	msghdr_tos = false;	/* Message header was displayed at Top of Screen */
 	int		row=0;			/* Current row */
diff --git a/src/sbbs3/scfglib.h b/src/sbbs3/scfglib.h
index ccdad5f687..f46c405f9d 100644
--- a/src/sbbs3/scfglib.h
+++ b/src/sbbs3/scfglib.h
@@ -51,7 +51,7 @@ DLLEXPORT void	free_chat_cfg(scfg_t* cfg);
 
 DLLEXPORT uint32_t aftou32(const char *str);      /* Converts flag string to uint32_t */
 DLLEXPORT char*	u32toaf(uint32_t t, char *str); /* Converts uint32_t to flag string */
-uint	attrstr(const char *str);		/* Convert ATTR string into attribute int */
+uint	strtoattr(const char *str, char** endptr);		/* Convert ATTR string into attribute int */
 
 int		getdirnum(scfg_t*, const char* code);
 int		getlibnum(scfg_t*, const char* code);
diff --git a/src/sbbs3/scfglib2.c b/src/sbbs3/scfglib2.c
index 279a9e9656..72f4cf9a9f 100644
--- a/src/sbbs3/scfglib2.c
+++ b/src/sbbs3/scfglib2.c
@@ -768,7 +768,7 @@ char *u32toaf(uint32_t l,char *str)
 /****************************************************************************/
 /* Returns the actual attribute code from a string of ATTR characters       */
 /****************************************************************************/
-uint attrstr(const char *str)
+uint strtoattr(const char *str, char** endptr)
 {
 	int atr;
 	ulong l=0;
@@ -833,9 +833,15 @@ uint attrstr(const char *str)
 			case '7':	/* White Background */
 				atr=(uchar)((atr&0x8f)|BG_LIGHTGRAY);
 				break;
+			default:
+				if(endptr != NULL)
+					*endptr = (char*)str + l;
+				return atr;
 			}
 		l++;
 	}
+	if(endptr != NULL)
+		*endptr = (char*)str + l;
 	return(atr);
 }
 
-- 
GitLab