aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Kangas2021-10-13 00:04:23 +0200
committerStefan Kangas2021-10-28 22:21:16 +0200
commit2671ea0de8e90e20241fe0441f4f8b79eeccdb12 (patch)
tree8f7706ebb6178963fefd94131b2de5ddfd3a58c9
parent64cc31b5c80ab165c4e565ff8943919d832ebd2f (diff)
downloademacs-2671ea0de8e90e20241fe0441f4f8b79eeccdb12.tar.gz
emacs-2671ea0de8e90e20241fe0441f4f8b79eeccdb12.zip
Be more allowing when looking for menu-bar items
* src/keymap.c (lookup_key_1): Factor out function from Flookup_key. (Flookup_key): Be case insensitive, and treat spaces as dashes, when looking for Qmenu_bar items. (Bug#50752) * test/src/keymap-tests.el (keymap-lookup-key/mixed-case) (keymap-lookup-key/mixed-case-multibyte) (keymap-lookup-keymap/with-spaces) (keymap-lookup-keymap/with-spaces-multibyte) (keymap-lookup-keymap/with-spaces-multibyte-lang-env): New tests.
-rw-r--r--etc/NEWS12
-rw-r--r--src/keymap.c161
-rw-r--r--test/src/keymap-tests.el43
3 files changed, 195 insertions, 21 deletions
diff --git a/etc/NEWS b/etc/NEWS
index f006fa530f3..cc452211b68 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -426,6 +426,18 @@ This returns the width of a string in pixels. This can be useful when
426dealing with variable pitch fonts and glyphs that have widths that 426dealing with variable pitch fonts and glyphs that have widths that
427aren't integer multiples of the default font. 427aren't integer multiples of the default font.
428 428
429---
430** 'lookup-key' is more allowing when searching for extended menu items.
431In Emacs 28.1, the behavior of 'lookup-key' was changed: when looking
432for a menu item '[menu-bar Foo-Bar]', first try to find an exact
433match, then look for the lowercased '[menu-bar foo-bar]'.
434
435This has been extended, so that when looking for a menu item with a
436symbol containing spaces, as in '[menu-bar Foo\ Bar]', first look for
437an exact match, then the lowercased '[menu-bar foo\ bar]' and finally
438'[menu-bar foo-bar]'. This further improves backwards-compatibility
439when converting menus to use 'easy-menu-define'.
440
429 441
430* Changes in Emacs 29.1 on Non-Free Operating Systems 442* Changes in Emacs 29.1 on Non-Free Operating Systems
431 443
diff --git a/src/keymap.c b/src/keymap.c
index 8b521a89dfa..2e98b059192 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -65,6 +65,9 @@ static Lisp_Object exclude_keys;
65/* Pre-allocated 2-element vector for Fcommand_remapping to use. */ 65/* Pre-allocated 2-element vector for Fcommand_remapping to use. */
66static Lisp_Object command_remapping_vector; 66static Lisp_Object command_remapping_vector;
67 67
68/* Char table for the backwards-compatibility part in Flookup_key. */
69static Lisp_Object unicode_case_table;
70
68/* Hash table used to cache a reverse-map to speed up calls to where-is. */ 71/* Hash table used to cache a reverse-map to speed up calls to where-is. */
69static Lisp_Object where_is_cache; 72static Lisp_Object where_is_cache;
70/* Which keymaps are reverse-stored in the cache. */ 73/* Which keymaps are reverse-stored in the cache. */
@@ -1209,27 +1212,8 @@ remapping in all currently active keymaps. */)
1209 return FIXNUMP (command) ? Qnil : command; 1212 return FIXNUMP (command) ? Qnil : command;
1210} 1213}
1211 1214
1212/* Value is number if KEY is too long; nil if valid but has no definition. */ 1215static Lisp_Object
1213/* GC is possible in this function. */ 1216lookup_key_1 (Lisp_Object keymap, Lisp_Object key, Lisp_Object accept_default)
1214
1215DEFUN ("lookup-key", Flookup_key, Slookup_key, 2, 3, 0,
1216 doc: /* Look up key sequence KEY in KEYMAP. Return the definition.
1217A value of nil means undefined. See doc of `define-key'
1218for kinds of definitions.
1219
1220A number as value means KEY is "too long";
1221that is, characters or symbols in it except for the last one
1222fail to be a valid sequence of prefix characters in KEYMAP.
1223The number is how many characters at the front of KEY
1224it takes to reach a non-prefix key.
1225KEYMAP can also be a list of keymaps.
1226
1227Normally, `lookup-key' ignores bindings for t, which act as default
1228bindings, used when nothing else in the keymap applies; this makes it
1229usable as a general function for probing keymaps. However, if the
1230third optional argument ACCEPT-DEFAULT is non-nil, `lookup-key' will
1231recognize the default bindings, just as `read-key-sequence' does. */)
1232 (Lisp_Object keymap, Lisp_Object key, Lisp_Object accept_default)
1233{ 1217{
1234 bool t_ok = !NILP (accept_default); 1218 bool t_ok = !NILP (accept_default);
1235 1219
@@ -1271,6 +1255,141 @@ recognize the default bindings, just as `read-key-sequence' does. */)
1271 } 1255 }
1272} 1256}
1273 1257
1258/* Value is number if KEY is too long; nil if valid but has no definition. */
1259/* GC is possible in this function. */
1260
1261DEFUN ("lookup-key", Flookup_key, Slookup_key, 2, 3, 0,
1262 doc: /* Look up key sequence KEY in KEYMAP. Return the definition.
1263A value of nil means undefined. See doc of `define-key'
1264for kinds of definitions.
1265
1266A number as value means KEY is "too long";
1267that is, characters or symbols in it except for the last one
1268fail to be a valid sequence of prefix characters in KEYMAP.
1269The number is how many characters at the front of KEY
1270it takes to reach a non-prefix key.
1271KEYMAP can also be a list of keymaps.
1272
1273Normally, `lookup-key' ignores bindings for t, which act as default
1274bindings, used when nothing else in the keymap applies; this makes it
1275usable as a general function for probing keymaps. However, if the
1276third optional argument ACCEPT-DEFAULT is non-nil, `lookup-key' will
1277recognize the default bindings, just as `read-key-sequence' does. */)
1278 (Lisp_Object keymap, Lisp_Object key, Lisp_Object accept_default)
1279{
1280 Lisp_Object found = lookup_key_1 (keymap, key, accept_default);
1281 if (!NILP (found) && !NUMBERP (found))
1282 return found;
1283
1284 /* Menu definitions might use mixed case symbols (notably in old
1285 versions of `easy-menu-define'), or use " " instead of "-".
1286 The rest of this function is about accepting these variations for
1287 backwards-compatibility. (Bug#50752) */
1288
1289 /* Just skip everything below unless this is a menu item. */
1290 if (!VECTORP (key) || !(ASIZE (key) > 0)
1291 || !EQ (AREF (key, 0), Qmenu_bar))
1292 return found;
1293
1294 /* Initialize the unicode case table, if it wasn't already. */
1295 if (NILP (unicode_case_table))
1296 {
1297 unicode_case_table = uniprop_table (intern ("lowercase"));
1298 staticpro (&unicode_case_table);
1299 }
1300
1301 ptrdiff_t key_len = ASIZE (key);
1302 Lisp_Object new_key = make_vector (key_len, Qnil);
1303
1304 /* Try both the Unicode case table, and the buffer local one.
1305 Otherwise, we will fail for e.g. the "Turkish" language
1306 environment where 'I' does not downcase to 'i'. */
1307 Lisp_Object tables[2] = {unicode_case_table, Fcurrent_case_table ()};
1308 for (int tbl_num = 0; tbl_num < 2; tbl_num++)
1309 {
1310 /* First, let's try converting all symbols like "Foo-Bar-Baz" to
1311 "foo-bar-baz". */
1312 for (int i = 0; i < key_len; i++)
1313 {
1314 Lisp_Object key_item = Fsymbol_name (AREF (key, i));
1315 Lisp_Object new_item;
1316 if (!STRING_MULTIBYTE (key_item))
1317 new_item = Fdowncase (key_item);
1318 else
1319 {
1320 USE_SAFE_ALLOCA;
1321 ptrdiff_t size = SCHARS (key_item), n;
1322 if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n))
1323 n = PTRDIFF_MAX;
1324 unsigned char *dst = SAFE_ALLOCA (n);
1325 unsigned char *p = dst;
1326 ptrdiff_t j_char = 0, j_byte = 0;
1327
1328 while (j_char < size)
1329 {
1330 int ch = fetch_string_char_advance (key_item, &j_char, &j_byte);
1331 Lisp_Object ch_conv = CHAR_TABLE_REF (tables[tbl_num], ch);
1332 if (!NILP (ch_conv))
1333 CHAR_STRING (XFIXNUM (ch_conv), p);
1334 else
1335 CHAR_STRING (ch, p);
1336 p = dst + j_byte;
1337 }
1338 new_item = make_multibyte_string ((char *) dst,
1339 SCHARS (key_item),
1340 SBYTES (key_item));
1341 SAFE_FREE ();
1342 }
1343 ASET (new_key, i, Fintern (new_item, Qnil));
1344 }
1345
1346 /* Check for match. */
1347 found = lookup_key_1 (keymap, new_key, accept_default);
1348 if (!NILP (found) && !NUMBERP (found))
1349 break;
1350
1351 /* If we still don't have a match, let's convert any spaces in
1352 our lowercased string into dashes, e.g. "foo bar baz" to
1353 "foo-bar-baz". */
1354 for (int i = 0; i < key_len; i++)
1355 {
1356 Lisp_Object lc_key = Fsymbol_name (AREF (new_key, i));
1357
1358 /* If there are no spaces in this symbol, just skip it. */
1359 if (!strstr (SSDATA (lc_key), " "))
1360 continue;
1361
1362 USE_SAFE_ALLOCA;
1363 ptrdiff_t size = SCHARS (lc_key), n;
1364 if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n))
1365 n = PTRDIFF_MAX;
1366 unsigned char *dst = SAFE_ALLOCA (n);
1367
1368 /* We can walk the string data byte by byte, because UTF-8
1369 encoding ensures that no other byte of any multibyte
1370 sequence will ever include a 7-bit byte equal to an ASCII
1371 single-byte character. */
1372 memcpy (dst, SSDATA (lc_key), SBYTES (lc_key));
1373 for (int i = 0; i < SBYTES (lc_key); ++i)
1374 {
1375 if (dst[i] == ' ')
1376 dst[i] = '-';
1377 }
1378 Lisp_Object
1379 new_it = make_multibyte_string ((char *) dst, SCHARS (lc_key), SBYTES (lc_key));
1380 ASET (new_key, i, Fintern (new_it, Qnil));
1381 SAFE_FREE ();
1382 }
1383
1384 /* Check for match. */
1385 found = lookup_key_1 (keymap, new_key, accept_default);
1386 if (!NILP (found) && !NUMBERP (found))
1387 break;
1388 }
1389
1390 return found;
1391}
1392
1274/* Make KEYMAP define event C as a keymap (i.e., as a prefix). 1393/* Make KEYMAP define event C as a keymap (i.e., as a prefix).
1275 Assume that currently it does not define C at all. 1394 Assume that currently it does not define C at all.
1276 Return the keymap. */ 1395 Return the keymap. */
diff --git a/test/src/keymap-tests.el b/test/src/keymap-tests.el
index 13f47b45f81..fc4dce01827 100644
--- a/test/src/keymap-tests.el
+++ b/test/src/keymap-tests.el
@@ -124,6 +124,49 @@
124;; (ert-deftest keymap-lookup-key/accept-default () 124;; (ert-deftest keymap-lookup-key/accept-default ()
125;; ...) 125;; ...)
126 126
127(ert-deftest keymap-lookup-key/mixed-case ()
128 "Backwards compatibility behaviour (Bug#50752)."
129 (let ((map (make-keymap)))
130 (define-key map [menu-bar foo bar] 'foo)
131 (should (eq (lookup-key map [menu-bar foo bar]) 'foo))
132 (should (eq (lookup-key map [menu-bar Foo Bar]) 'foo)))
133 (let ((map (make-keymap)))
134 (define-key map [menu-bar i-bar] 'foo)
135 (should (eq (lookup-key map [menu-bar I-bar]) 'foo))))
136
137(ert-deftest keymap-lookup-key/mixed-case-multibyte ()
138 "Backwards compatibility behaviour (Bug#50752)."
139 (let ((map (make-keymap)))
140 ;; (downcase "Åäö") => "åäö"
141 (define-key map [menu-bar åäö bar] 'foo)
142 (should (eq (lookup-key map [menu-bar åäö bar]) 'foo))
143 (should (eq (lookup-key map [menu-bar Åäö Bar]) 'foo))
144 ;; (downcase "Γ") => "γ"
145 (define-key map [menu-bar γ bar] 'baz)
146 (should (eq (lookup-key map [menu-bar γ bar]) 'baz))
147 (should (eq (lookup-key map [menu-bar Γ Bar]) 'baz))))
148
149(ert-deftest keymap-lookup-keymap/with-spaces ()
150 "Backwards compatibility behaviour (Bug#50752)."
151 (let ((map (make-keymap)))
152 (define-key map [menu-bar foo-bar] 'foo)
153 (should (eq (lookup-key map [menu-bar Foo\ Bar]) 'foo))))
154
155(ert-deftest keymap-lookup-keymap/with-spaces-multibyte ()
156 "Backwards compatibility behaviour (Bug#50752)."
157 (let ((map (make-keymap)))
158 (define-key map [menu-bar åäö-bar] 'foo)
159 (should (eq (lookup-key map [menu-bar Åäö\ Bar]) 'foo))))
160
161(ert-deftest keymap-lookup-keymap/with-spaces-multibyte-lang-env ()
162 "Backwards compatibility behaviour (Bug#50752)."
163 (let ((lang-env current-language-environment))
164 (set-language-environment "Turkish")
165 (let ((map (make-keymap)))
166 (define-key map [menu-bar i-bar] 'foo)
167 (should (eq (lookup-key map [menu-bar I-bar]) 'foo)))
168 (set-language-environment lang-env)))
169
127(ert-deftest describe-buffer-bindings/header-in-current-buffer () 170(ert-deftest describe-buffer-bindings/header-in-current-buffer ()
128 "Header should be inserted into the current buffer. 171 "Header should be inserted into the current buffer.
129https://debbugs.gnu.org/39149#31" 172https://debbugs.gnu.org/39149#31"