1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
|
;;; general.el --- Convenience wrappers for keybindings. -*- lexical-binding: t -*-
;; Author: Fox Kiester <noct@openmailbox.org>
;; URL: https://github.com/noctuid/general.el
;; Package-Version: 20180901.1552
;; Created: February 17, 2016
;; Keywords: vim, evil, leader, keybindings, keys
;; Package-Requires: ((emacs "24.4") (cl-lib "0.5"))
;; Version: 0.1
;; This file is not part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This package provides convenient wrappers for more succinctly defining
;; keybindings. It allows defining multiple keys at once, specifying an
;; arbitrary number of named prefix keys to be used in key definitions,
;; implicitly wrapping key strings with (kbd ...), and more. It provides a
;; single function for standard emacs key definitions as well as evil key
;; definitions for any evil state and any keymap. It also provides a setup
;; function to generate "nmap", "vmap", etc. keybinding functions for evil.
;; For more information see the README in the online repository.
;;; Code:
(require 'cl-lib)
;; * Settings
(defgroup general nil
"Gives convenient wrappers for key definitions."
:group 'convenience
:prefix "general-")
(defcustom general-implicit-kbd t
"Whether to implicitly wrap a (kbd) around `general-define-key' keys.
This applies to the prefix key as well. This option is provided to make it easy
to transition from other key definers to `general-define-key'. It does not
apply to other helpers such as `general-key', `general-key-dispatch', and
`general-translate-key'. These will always use `kbd' on keys that are
strings."
:group 'general
:type 'boolean)
(defcustom general-default-prefix nil
"The default prefix key sequence to use."
:group 'general
:type 'string)
(make-obsolete-variable 'general-default-prefix
"This functionality will be removed in the future."
"2018-01-21")
(defcustom general-default-non-normal-prefix nil
"The default prefix key sequence to use for the 'emacs and 'insert states.
Note that this setting is only useful for evil-users and will only have an
effect when binding keys in the 'emacs and/or 'insert states or in the
'evil-insert-state-map and/or 'evil-emacs-state-map keymaps. When this is not
specified, `general-default-prefix' will be the default prefix for any states
and keymaps. If this is specified `general-default-prefix' or the arg to :prefix
will not be used when binding keys in the insert and emacs states."
:group 'general
:type 'string)
(make-obsolete-variable 'general-default-non-normal-prefix
"This functionality will be removed in the future."
"2018-01-21")
(defcustom general-default-global-prefix nil
"The default prefix key sequence to use for all evil states.
This setting is only useful for evil users. Note that like with
`general-default-non-normal-prefix', if this or :global-prefix is specified,
`general-default-prefix' or the arg to :prefix will not be used for binding
keys in the insert and emacs states. If you don't need a different or extra
prefix for one or both state types (insert and emacs vs. the other states),
just use `general-default-prefix'/:prefix by itself."
:group 'general
:type 'string)
(make-obsolete-variable 'general-default-global-prefix
"This functionality will be removed in the future."
"2018-01-21")
(define-widget 'general-state 'lazy
"General's evil state type."
:type '(choice
(const :tag "Insert state" insert)
(const :tag "Emacs state" emacs)
(const :tag "Normal state" normal)
(const :tag "Visual state" visual)
(const :tag "Motion state" motion)
(const :tag "Operator state" operator)
(const :tag "Replace state" replace)
(const :tag "Use define-key not evil-define-key" nil)
;; other packages define states
symbol))
(defcustom general-default-states nil
"The default evil state(s) to make mappings in.
Non-evil users should keep this nil."
:group 'general
:type '(choice general-state
(set general-state)))
(make-obsolete-variable 'general-default-states
"This functionality will be removed in the future."
"2018-01-21")
(defcustom general-non-normal-states '(insert emacs hybrid iedit-insert)
"List of \"non-normal\" evil states (used with :non-normal-prefix). When
:states is not specified (only :keymaps), these will automatically be expanded
to their full global evil keymap equivalents."
:group 'general
:type '(repeat general-state))
(define-widget 'general-keymap 'lazy
"General's keymap type."
:type '(choice
(const :tag "Global keymap" global)
(const :tag "Buffer local keymap" local)
symbol))
(defcustom general-default-keymaps 'global
"The default keymap(s) to bind keys in."
:group 'general
:type '(choice general-keymap
(repeat general-keymap)))
(make-obsolete-variable 'general-default-keymaps
"This functionality will be removed in the future."
"2018-01-21")
(defcustom general-vim-definer-default nil
"Whether set the states or keymaps in a `general-create-vim-definer' function.
If nil, use the default from when the function was created. If 'keymaps, set the
default keymaps. If 'states, set the default states."
:group 'general
:type '(choice
(const :tag "Default to setting :keymaps" keymaps)
(const :tag "Default to setting :states" states)
(const :tag "Use the initial default" nil)))
(make-obsolete-variable 'general-vim-definer-default
"This functionality is no longer necessary."
"2018-01-20")
(defvar general-keybindings nil
"Holds all the keybindings created with `general-define-key' (and wrappers).
This is an alist of a keymap to an alist of a state to keybindings.")
(defvar general-local-keybindings nil
"Holds all the local keybindings created with `general-define-key'.
This is an alist of a state to keybindings.")
(make-variable-buffer-local 'general-local-keybindings)
(define-widget 'general-alist 'lazy
"General's alist type."
:type '(alist :key-type (or symbol (repeat symbol))
:value-type symbol))
(defcustom general-keymap-aliases
'((override . general-override-mode-map)
((i insert) . evil-insert-state-map)
((e emacs) . evil-emacs-state-map)
((h hybrid) . evil-hybrid-state-map)
((n normal) . evil-normal-state-map)
((v visual) . evil-visual-state-map)
((m motion) . evil-motion-state-map)
((o operator) . evil-operator-state-map)
((r replace) . evil-replace-state-map)
((in inner) . evil-inner-text-objects-map)
((out outer) . evil-outer-text-objects-map))
"An alist for mapping short keymap names to their full names.
Earlier entries have higher precedence."
:group 'general
:type 'general-alist)
(defcustom general-state-aliases
'((i . insert)
(e . emacs)
(h . hybrid)
(n . normal)
(v . visual)
(m . motion)
(o . operator)
(r . replace))
"An alist for mapping short state names to their full names.
Earlier entries have higher precedence."
:group 'general
:type 'general-alist)
;; ** `general-describe-keybindings' Settings
(defcustom general-describe-keybinding-sort-function nil
"Function used to sort keybindings for `general-describe-keybindings'."
:group 'general
:type '(choice function (const nil)))
(defcustom general-describe-state-sort-function
#'general--sort-evil-state-conses
"Function used to sort the states conses for `general-describe-keybindings'."
:group 'general
:type '(choice function (const nil)))
(defcustom general-describe-keymap-sort-function nil
"Function used to sort the keymap conses`general-keybindings' for
`general-describe-keybindings'."
:group 'general
:type '(choice function (const nil)))
(defcustom general-describe-priority-keymaps
'(local
global
evil-insert-state-map
evil-emacs-state-map
evil-hybrid-state-map
evil-normal-state-map
evil-visual-state-map
evil-motion-state-map
evil-operator-state-map
evil-replace-state-map
evil-inner-text-objects-map
evil-outer-text-objects-map
evil-ex-search-keymap
evil-ex-completion-map
evil-command-window-mode-map
evil-window-map)
"Keymaps to print first for `general-describe-keybindings'."
:group 'general
:type '(repeat sybmol))
(defcustom general-describe-update-previous-definition 'on-change
"Whether to update the previous definition when a key is bound.
When set to 'on-change, the previous definition will only be updated when the
definition changes (e.g. re-evaluating a file with keybindings will not affect
the stored previous definition). When set to nil, it will only be updated when
the key was previously unbound."
:group 'general
;; can't think of a use case, but add 'always if requested
;; t is equivalent of on-change
:type '(choice
(const :tag "When definition has changed" on-change)
(const :tag "When the key was previously unbound" nil)))
;; * Override Minor Modes
(defcustom general-override-auto-enable t
"Whether to automatically enable `general-override-mode'.
If non-nil, enable `general-override-mode' when binding a key in
`general-override-mode-map'."
:group 'general
:type 'boolean)
(defvar general-override-mode-map (make-sparse-keymap)
"A keymap that will take priority over other minor mode keymaps.
This is only for non-evil keybindings (it won't override keys bound with
`evil-define-key'.")
(define-minor-mode general-override-mode
"A global minor mode used for key definitions that should override others."
:lighter ""
:global t
:require 'general
:keymap general-override-mode-map)
(defvar-local general-override-local-mode-map nil
"A keymap that will take priority over other minor mode keymaps.
This keymap is buffer-local and will take precedence over
`general-override-mode-map'. General uses this keymap when creating non-evil
local keybindings.")
(put 'general-override-local-mode-map 'permanent-local t)
(define-minor-mode general-override-local-mode
"A local minor mode used for key definitions that should override others."
:lighter ""
:keymap general-override-local-mode-map)
(defvar-local general-maps-alist
`((general-override-mode . ,general-override-mode-map))
"Holds the (mode . keymap) pairs for general's override modes.")
;; not affected by changing major modes
(put 'general-maps-alist 'permanent-local t)
(defvar-local general--maps-alist-updated nil
"Whether `general-maps-alist' has been set correctly for the current buffer.")
(put 'general-maps-alist 'permanent-local t)
(declare-function evil-make-intercept-map "evil-core")
(defun general-override-make-intercept-maps (_sym states)
"Make intercept keymaps for STATES in `general-override-mode-map'.
This means that keys bound in STATES for `general-override-mode-map' will take
precedence over keys bound in other evil auxiliary maps."
;; can't use `general-with-eval-after-load' here; not available
(with-eval-after-load 'evil
;; TODO eventually use new evil-make-intercept-map arg
(dolist (state states)
(evil-make-intercept-map
(evil-get-auxiliary-keymap general-override-mode-map state t t)
state))))
(defcustom general-override-states
'(insert
emacs
hybrid
normal
visual
motion
operator
replace)
"States to make intercept maps for in `general-override-mode-map'.
Note that this uses :set, meaning that if you want to change the value, you
should either set it using customize (e.g. `general-setq' or
`customize-set-variable') or set it before loading general if using `setq'."
:group 'general
:type '(repeat general-state)
:set #'general-override-make-intercept-maps)
(defun general--update-maps-alist ()
"Update `general-maps-alist' for override modes.
This is necessary to ensure `general-override-local-mode-map' is the buffer's
local version."
(setq general-maps-alist
`((general-override-local-mode . ,general-override-local-mode-map)
(general-override-mode . ,general-override-mode-map))
general--maps-alist-updated t))
(cl-pushnew 'general-maps-alist emulation-mode-map-alists)
(defun general-local-map ()
"Return `general-override-local-mode-map', initializing it if necessary.
Also turn on `general-override-local-mode' and update `general-maps-alist'."
(or general-override-local-mode (general-override-local-mode))
(unless (and general-override-local-mode-map
(local-variable-p 'general-override-local-mode-map))
(setq general-override-local-mode-map (make-sparse-keymap)))
(unless general--maps-alist-updated
(general--update-maps-alist))
general-override-local-mode-map)
;; * General Helpers
(defmacro general-with-eval-after-load (file &rest body)
"Like `with-eval-after-load' but don't always add to `after-load-alist'.
When FILE has already been loaded, execute BODY immediately without adding it to
`after-load-alist'."
(declare (indent 1))
`(if (if (stringp ,file)
(load-history-filename-element
(purecopy (load-history-regexp ,file)))
(featurep ,file))
(progn ,@body)
(eval-after-load ,file (lambda () ,@body))))
(defun general--unalias (symbol &optional statep)
"Return the full keymap or state name associated with SYMBOL.
If STATEP is non-nil, check `general-state-aliases' instead of
`general-keymap-aliases'."
(let ((match
(cdr (cl-assoc symbol
(if statep
general-state-aliases
general-keymap-aliases)
;; test-fn is new to assoc in 26.1
:test (lambda (symbol key)
(or (eq symbol key)
(ignore-errors (memq symbol key))))))))
(or match symbol)))
;; don't want to reuse `general--unalias' since the user can alter
;; `general-keymap-aliases'
(defun general--evil-keymap-for-state (state)
"Return a symbol corresponding to the global evil keymap for STATE."
(intern (concat "evil-" (symbol-name state) "-state-map")))
(defun general--kbd (key)
"Use `kbd' on KEY when it is a string."
(if (stringp key)
(kbd key)
key))
;; TODO refactor to be more straightforward
(defun general--concat (nokbd &rest keys)
"Concatenate the strings in KEYS.
If `general-implicit-kbd' is non-nil, interleave the strings in KEYS with
spaces; unless NOKBD is non-nil, apply (kbd ...) to the result. If
`general-implicit-kbd' is nil, just concatenate the keys."
(setq keys (remove nil keys))
(if general-implicit-kbd
(let ((keys (mapconcat (lambda (x)
(if (vectorp x)
(key-description x)
x))
keys " ")))
(if nokbd
keys
(kbd keys)))
(apply #'concat keys)))
(defun general--apply-prefix-and-kbd (prefix maps)
"Prepend the PREFIX sequence to all the keys that are strings in MAPS.
Also apply (kbd ...) to key and definition strings if `general-implicit-kbd' is
non-nil."
(setq prefix (or prefix ""))
(cl-loop for (key def) on maps by 'cddr
collect (general--concat nil prefix key)
and collect def))
(defun general--lookup-key (state keymap key &optional minor-mode-p)
"Return the current definition for STATE, KEYMAP, and KEY."
(when key
(let ((keymap (general--get-keymap state keymap minor-mode-p)))
(when keymap
(let ((def (lookup-key keymap key)))
(if (and (numberp def) (= def 1))
nil
def))))))
(defun general--record-keybindings (keymap state maps &optional minor-mode-p)
"For KEYMAP and STATE, add MAPS to `general-keybindings'.
If KEYMAP is \"local\", add MAPS to `general-local-keybindings.' For non-evil
keybindings, STATE will be nil. Duplicate keys will be replaced with the new
ones. MINOR-MODE-P should be non-nil when keymap corresponds to a minor-mode
name (as used with `evil-define-minor-mode-key') as opposed to a keymap name."
(if (and state (not (featurep 'evil)))
(general-with-eval-after-load 'evil
(general--record-keybindings keymap state maps minor-mode-p))
(let* (keys
(maps (cl-loop
for (key new-def _orig-def) on maps by 'cl-cdddr
collect
(list key
new-def
(let* ((current-def (general--lookup-key
state keymap key minor-mode-p))
;; none of these will fail if nil
(keymap-cons (assq keymap general-keybindings))
(state-cons (assq state (cdr keymap-cons)))
(mapping (cl-find key (cdr state-cons)
:test #'equal :key #'car))
(previous-def (cl-caddr mapping)))
(if (or
(and current-def (not previous-def))
(and general-describe-update-previous-definition
(not (equal new-def current-def))))
current-def
previous-def)))
do (push key keys))))
(cond ((eq keymap 'local)
(unless (assq state general-local-keybindings)
(add-to-list 'general-local-keybindings (list state)))
(let ((state-cons (assq state general-local-keybindings)))
(setcdr state-cons
;; remove old duplicate keys
(cl-remove-duplicates (append (cdr state-cons) maps)
:key #'car
:test #'equal))))
(t
(unless (assq keymap general-keybindings)
(add-to-list 'general-keybindings (list keymap)))
(unless (assq state (assq keymap general-keybindings))
(setcdr (assq keymap general-keybindings)
(append (cdr (assq keymap general-keybindings))
(list (list state)))))
(let ((state-cons (assq state (assq keymap general-keybindings))))
(setcdr state-cons
(cl-remove-duplicates (append (cdr state-cons) maps)
:key #'car
:test #'equal))))))))
;; don't force non-evil user to require evil for one function
(defun general--delay (condition form hook &optional append local name)
"Execute FORM when CONDITION becomes true, checking with HOOK.
NAME specifies the name of the entry added to HOOK. If APPEND is
non-nil, the entry is appended to the hook. If LOCAL is non-nil,
the buffer-local value of HOOK is modified.
This is `evil-delay'."
(declare (indent 2))
(if (and (not (booleanp condition)) (eval condition))
(eval form)
(let* ((name (or name (format "general-delay-form-in-%s" hook)))
(fun (make-symbol name))
(condition (or condition t)))
(fset fun `(lambda (&rest args)
(when ,condition
(remove-hook ',hook #',fun ',local)
,form)))
(put fun 'permanent-local-hook t)
(add-hook hook fun append local))))
(defun general--getf (def fallback-plist keyword)
"From DEF or FALLBACK-PLIST get the corresponding value for KEYWORD.
FALLBACK-PLIST will be checked when KEYWORD does not exist in DEF (not in cases
where it is explicitly specified as nil). If DEF isn't a general extended
definition, only check in FALLBACK-PLIST."
(if (general--extended-def-p def)
(cl-getf def keyword
(cl-getf fallback-plist keyword))
(cl-getf fallback-plist keyword)))
(defun general--getf2 (plist keyword1 keyword2)
"Check in PLIST for either KEYWORD1 or KEYWORD2."
(or (cl-getf plist keyword1)
(cl-getf plist keyword2)))
(declare-function evil-get-minor-mode-keymap "evil-core")
(declare-function evil-state-property "evil-common")
(declare-function evil-get-auxiliary-keymap "evil-core")
(cl-defun general--get-keymap (state &optional keymap
minor-mode
ignore-special)
"Transform STATE and the symbol or keymap KEYMAP into the appropriate keymap.
If MINOR-MODE and STATE are non-nil, use `evil-get-minor-mode-keymap'. If
IGNORE-SPECIAL is non-nil, do not try to resolve the \"special\" keymaps 'global
and 'local. In this case, the only thing this function will do is return the
actually keymap if KEYMAP is a symbol besides 'global or 'local. Otherwise the
keymap returned depends on whether STATE is specified. Note that if STATE is
specified, evil needs to be installed and will be required.
STATE nil:
'local - Run/return `general-local-map'
'global - Run/return `current-global-map'
else - Return keymap or (symbol-value keymap)
STATE non-nil:
'local - Return the corresponding evil local map
'global - Return the corresponding evil global map
else - Return the corresponding evil auxiliary or minor mode map"
(when (and (symbolp keymap)
(not (memq keymap '(global local))))
(setq keymap (symbol-value keymap)))
(when ignore-special
(cl-return-from general--get-keymap keymap))
(if state
(if (require 'evil nil t)
(cond ((or (null keymap)
(eq keymap 'global))
(evil-state-property state :keymap t))
(minor-mode
(evil-get-minor-mode-keymap state keymap))
((eq keymap 'local)
(evil-state-property state :local-keymap t))
(t
(evil-get-auxiliary-keymap keymap state t t)))
(error "Evil is required if state is specified"))
(cl-case keymap
(global (current-global-map))
(local (general-local-map))
(t keymap))))
(define-obsolete-function-alias 'general--parse-keymap 'general--get-keymap
"2018-01-14")
(defun general--remove-keyword-args (rest)
"Remove all keyword arguments from the list REST.
Return a list of the altered REST list and a list of the removed keyword
arguments. The order of arguments will be preserved. Note that the length of
REST does not need to be even (i.e. there can be an odd number of positional
arguments)."
(let (args
kargs)
(while rest
(cond ((keywordp (car rest))
(push (pop rest) kargs)
(push (pop rest) kargs))
(t
(push (pop rest) args))))
(list (nreverse args) (nreverse kargs))))
(defmacro general--ensure-lists (&rest vars)
"Ensure that all variables in VARS are lists if they are not already.
If any variable is a lambda, it will not be considered to be a list. If a var is
nil, it will be set to (list nil)."
`(progn
,@(mapcar (lambda (var)
`(unless (and ,var
(listp ,var)
;; lambdas are lists
(not (functionp ,var)))
(setq ,var (list ,var))))
vars)))
;; * Extended Key Definition Language
;; ** Variables
(defvar general-extended-def-keywords
'(:which-key :wk :properties :repeat :jump)
"Extra keywords that are valid for extended definitions.
These can work both locally (in extended definitions) and globally (in which
case they apply to all definitions including normal ones). Note that not all
keywords need to make sense/work globally. If the keyword should be ignored when
used globally, add it to `general-extended-def-global-ignore-keywords' as well.
For each keyword there should be a corresponding function named
general-extended-def-:<keyword> which will be passed state, keymap (the symbol
not actual keymap), key (the internal representation, i.e. `kbd' already called
if necessary), edef (always a plist; normal definitions will automatically be
converted), and kargs (the original `general-define-key' keyword argument plist;
useful when the keyword can be used globally or has helper keywords that can be
used globally). This function is only called for side effects; if you actually
need to alter the definition, you should add the keyword to
`general-rewrite-def-keywords' or `general-rewrite-def-after-keywords' instead.
The order of those lists matters, but the order of this list does not.
`general--get-keymap' may be useful for getting the actual keymap from the
keymap symbol. `general--getf' may be useful for keywords (helper or main) that
can be specified globally (in kargs) and overridden locally (in def).")
(defvar general-rewrite-def-keywords
'(:keymap :prefix-command :prefix-keymap)
"Extended definition keywords that alter the definition.
Each keyword should have a corresponding function named
general-extended-def-:<keyword> and should return a new extended definition
plist (with an altered :def entry). See `general-extended-def-keywords' for
information on the arguments this function should take. These functions will be
run in the order they appear in this list, and each will be passed the most
recent version of the extended definition plist.
In contrast to the functions for `general-rewrite-def-after-keywords', these
functions will alter the definition before any `general-extended-def-keyword'
functions run. For example, if your function creates a newly named wrapper
command around the user-specified command, you'd want to add the keyword to this
list, so that `general-extended-def-keywords' functions would have access to new
command name (e.g. for :which-key to work properly). On the other hand, if the
keyword, for example, involves putting the definition in an extended menu item
like with :predicate, you should add to `general-rewrite-def-after-keywords'
instead.")
(defvar general-rewrite-def-after-keywords
'(:predicate)
"Extended definition keywords that alter the definition.
See `general-rewrite-def-keywords' for more information.")
(defvar general-extended-def-global-ignore-keywords
'(:keymap :prefix-command :prefix-map)
"Extended definitions that should be ignored when used globally.
For example, :prefix-command and :prefix-map are handled differently when used
globally (they have special interaction with other global keywords). :keymap, on
the other hand, doesn't make sense at all globally.")
;; ** Normal Extended Definition Functions
;; *** Which Key Integration
(defvar which-key-replacement-alist)
(defun general--add-which-key-replacement (mode replacement)
(let* ((mode-match (assq mode which-key-replacement-alist))
(mode-alist (cdr mode-match)))
(cond (mode
(push replacement mode-alist)
(if mode-match
(setcdr mode-match mode-alist)
(push (cons mode mode-alist)
which-key-replacement-alist)))
(t
(push replacement which-key-replacement-alist)))))
(defvar which-key--prefix-title-alist)
(defun general--add-which-key-title-prefix (mode keys title-prefix)
(let* ((mode-match (assq mode which-key--prefix-title-alist))
(title-mode-alist (cdr mode-match))
(title-cons (cons keys title-prefix)))
(cond (mode
(push title-cons title-mode-alist)
(if mode-match
(setcdr mode-match
title-mode-alist)
(push (cons mode title-mode-alist)
which-key--prefix-title-alist)))
(t
(push title-cons which-key--prefix-title-alist)))))
(defun general--remove-map (keymap)
"Remove \"-map\" from the symbol KEYMAP." ;
(intern (replace-regexp-in-string "-map$" "" (symbol-name keymap))))
;; TODO better documentation
(defun general-extended-def-:which-key (_state keymap key edef kargs)
"Add a which-key description for KEY.
If :major-modes is specified in EDEF, add the description for the corresponding
major mode. KEY should not be in the kbd format (kbd should have already been
run on it)."
(general-with-eval-after-load 'which-key
(let* ((wk (general--getf2 edef :which-key :wk))
(major-modes (general--getf edef kargs :major-modes))
(keymaps (plist-get kargs :keymaps))
;; index of keymap in :keymaps
(keymap-index (cl-dotimes (ind (length keymaps))
(when (eq (nth ind keymaps) keymap)
(cl-return ind))))
(mode (let ((mode (if (and major-modes (listp major-modes))
(nth keymap-index major-modes)
major-modes)))
(if (eq mode t)
(general--remove-map keymap)
mode)))
(key (key-description key))
(key-regexp (concat (when (general--getf edef kargs :wk-full-keys)
"\\`")
(regexp-quote key)
"\\'"))
(prefix (plist-get kargs :prefix))
(binding (or (when (and (plist-get edef :def)
(not (plist-get edef :keymp)))
(plist-get edef :def))
(when (and prefix
(string= key prefix))
(plist-get kargs :prefix-command))))
(replacement (cond ((stringp wk)
(cons nil wk))
(t
wk)))
(match/replacement
(cons
(cons (when (general--getf edef kargs :wk-match-keys)
key-regexp)
(when (and (general--getf edef kargs :wk-match-binding)
binding
(symbolp binding))
(symbol-name binding)))
replacement)))
(general--add-which-key-replacement mode match/replacement)
(when (and (consp replacement)
;; lambda
(not (functionp replacement)))
(general--add-which-key-title-prefix
mode key (cdr replacement))))))
(defalias 'general-extended-def-:wk #'general-extended-def-:which-key)
;; *** Evil Integration
(declare-function evil-add-command-properties "evil-common")
(defun general-extended-def-:properties (_state _keymap _key edef kargs)
"Use `evil-add-command-properties' to add properties to a command.
The properties should be specified with :properties in either EDEF or KARGS."
(general-with-eval-after-load 'evil
(let ((properties (general--getf edef kargs :properties))
(command (cl-getf edef :def)))
(apply #'evil-add-command-properties command properties))))
(defun general-extended-def-:repeat (_state _keymap _key edef kargs)
"Use `evil-add-command-properties' to set the :repeat property for a command.
The repeat property should be specified with :repeat in either EDEF or KARGS."
(general-with-eval-after-load 'evil
(let ((repeat-property (general--getf edef kargs :repeat))
(command (cl-getf edef :def)))
(evil-add-command-properties command :repeat repeat-property))))
(defun general-extended-def-:jump (_state _keymap _key edef kargs)
"Use `evil-add-command-properties' to set the :jump property for a command.
The jump property should be specified with :jump in either EDEF or KARGS."
(general-with-eval-after-load 'evil
(let ((jump-property (general--getf edef kargs :jump))
(command (cl-getf edef :def)))
(evil-add-command-properties command :jump jump-property))))
;; ** Extended Defintion Functions That Alter the Definition
(defun general-extended-def-:keymap (state keymap _key edef kargs)
"Return an extended definition for a keymap or a \"autoloaded\" keymap.
If the specified keymap does not exist, create a function that binds the keys it
was invoked with in STATE and KEYMAP to the keymap specified in the extended
definition EDEF and then act as if it was originally bound to that
keymap (subsequent keys will be looked up in the keymap). KARGS or EDEF should
contain the package in which the keymap is created (as specified with :package).
If the keymap already exists, it will simply be returned."
(let ((bind-keymap-sym (plist-get edef :def))
(package (general--getf edef kargs :package))
(definer (general--getf edef kargs :definer)))
(if (boundp bind-keymap-sym)
(setf (cl-getf edef :def) (symbol-value bind-keymap-sym))
(if package
(setf (cl-getf edef :def)
;; relying on lexical binding here
(lambda ()
(interactive)
(unless (or (featurep package)
(require package nil t))
(error (format "Failed to load package: %s" package)))
(unless (and (boundp bind-keymap-sym)
(keymapp (symbol-value bind-keymap-sym)))
(error (format
"A keymap called %s is not defined in the %s package"
bind-keymap-sym package)))
;; use `this-command-keys' as `key' may not be the full sequence
(let ((keys (this-command-keys))
(general-implicit-kbd nil))
(general-define-key
:states state
:keymaps keymap
:definer definer
keys (symbol-value bind-keymap-sym))
(setq prefix-arg current-prefix-arg
unread-command-events
(mapcar (lambda (ev) (cons t ev))
(listify-key-sequence keys))))))
(error "In order to \"autoload\" a keymap, :package must be specified"))))
edef)
(defun general--define-prefix (command-name &optional map-name menu-name)
"Define a prefix command and/or keymap.
COMMAND-NAME corresponds to the prefix command name. When COMMAND-NAME is
non-nil, `define-prefix-command' will be used and will be passed MAP-NAME and
MENU-NAME. When COMMAND-NAME is nil and MAP-NAME is non-nil, only a prefix
keymap will be created, and its menu name/prompt will be set to MENU-NAME (if
MENU-NAME is non-nil). Existing prefix keymaps/commands will not be
recreated/rebound."
(if (or (and command-name (fboundp command-name))
(and map-name (boundp map-name)))
(or command-name (symbol-value map-name))
(cond (command-name
(define-prefix-command command-name map-name menu-name))
(map-name
(eval `(defvar ,map-name (make-sparse-keymap ,menu-name)))))))
(defun general-extended-def-:prefix-command (_state _keymap _key edef _kargs)
"Create and return a prefix command or map for the extended definition EDEF.
The :prefix-command, :prefix-map, and :prefix-name properties from EDEF are
passed to `general--define-prefix'."
;; NOTE will be called twice if both specified, but doesn't matter because
;; won't recreate prefix-command
(setf (cl-getf edef :def)
(general--define-prefix (plist-get edef :prefix-command)
(plist-get edef :prefix-map)
(plist-get edef :prefix-name)))
edef)
(defalias 'general-extended-def-:prefix-map
#'general-extended-def-:prefix-command)
;; http://endlessparentheses.com/define-context-aware-keys-in-emacs.html
(defun general-extended-def-:predicate (_state _keymap _key edef kargs)
"Return an altered extended definition EDEF with a predicate applied.
The predicate is obtained either from EDEF or KARGS."
(let ((def (cl-getf edef :def))
(predicate (general--getf edef kargs :predicate)))
(setf (cl-getf edef :def)
`(menu-item
"" nil
:filter (lambda (&optional _)
(when ,predicate
',def))))
edef))
;; ** Parsing Extended Definitions
(defun general--extended-def-p (def)
"Return whether DEF is an extended definition."
(and (listp def)
(not (keymapp def))
;; lambda
(not (functionp def))
(not (eq (car def) 'menu-item))
;; will error on cons
(ignore-errors (cl-some #'keywordp def))))
(defun general--normalize-extended-def (edef)
"Rewrite the extended definition EDEF to include a :def property.
If EDEF is not an extended defintion, make it into one.
This handles the allowed shorthand syntax. For example, these are the same:
(some-func)
(:def some-func)
Some extended definition keywords can be used instead of :def (mainly for
backwards compatibility). For example, these are the same:
(some-keymap :keymap t)
(:keymap some-keymap)
(:def some-keymap :keymap t)"
;; NOTE: This is absolutely necessary for plist functions to work
(if (general--extended-def-p edef)
(unless (keywordp (car edef))
(setq edef (cons :def edef)))
(setq edef (list :def edef)))
;; :keymap checks :def always instead of :keymap, and :which-key also checks
;; :def always (instead of :prefix-command)
;; note that :keymap and :prefix-map will later rewrite their :def to the
;; actual keymap value
(unless (plist-get edef :def)
(setf (cl-getf edef :def)
(cl-getf edef :keymap
(cl-getf edef :prefix-command
(plist-get edef :prefix-map)))))
edef)
(defun general--extract-def (edef)
"Return the bindable definition from the extended definition EDEF."
(if (plist-get edef :ignore)
;; just for side effects (e.g. which-key description for prefix)
;; return something that isn't a valid definition
:ignore
(plist-get edef :def)))
(defun general--run-extended-def-functions (state keymap key edef kargs)
"Run the extended definition functions for the matched keywords.
Pass each extended definition function STATE, KEYMAP, KEY, EDEF, and KARGS. For
each keyword from `general-extended-def-keywords',
`general-rewrite-def-keywords', and `general-rewrite-def-after-keywords' found
in EDEF or KARGS, call the corresponding function named
general-extended-def-:<keyword>. The functions for
`general-rewrite-def-keywords' will rewrite the extended definition plist before
the functions for `general-extended-def-keywords' are called, and the functions
for `general-rewrite-def-after-keywords' are called after that. Functions
are called in the order they appear in each list. Finally, return the
potentially altered extended definition plist."
(cl-flet ((run-edef-functions
(keywords &optional alter-def)
(dolist (keyword keywords)
(when (or (plist-get edef keyword)
(and (not
(memq
keyword
general-extended-def-global-ignore-keywords))
(plist-get kargs keyword)))
(let ((ret (funcall
(intern (format "general-extended-def-%s" keyword))
state keymap key edef kargs)))
(when alter-def
(setq edef ret)))))))
(run-edef-functions general-rewrite-def-keywords t)
(run-edef-functions general-extended-def-keywords)
(run-edef-functions general-rewrite-def-after-keywords t))
edef)
(defun general--parse-def (state keymap key def kargs)
"Rewrite DEF into a valid/bindable definition.
This function will execute all extended definitions, potentially rewriting the
original definition (e.g. applying a predicate). Pass STATE, KEYMAP, KEY, DEF, and
KARGS to each matched extended definition function. See
`general--run-extended-def-functions' for more information."
(setq def (general--normalize-extended-def def))
(general--extract-def
(general--run-extended-def-functions state keymap key def kargs)))
(defun general--parse-maps (state keymap maps kargs)
"Rewrite MAPS so that the definitions are bindable.
This includes possibly calling `kbd' on keys and parsing extended definitions.
Turn key/binding pairs in MAPS into triples in the form of (key parsed-def
original-def) where parsed-def is the bindable form and original-def is the
original definition as an extended definition plist (turn normal definitions
into extended definition plists and implicitly add \":def\" to the beginning of
extended definitions when necessary)."
(let (bindable-def)
(cl-loop for (key def) on maps by 'cddr
do (setq bindable-def
(general--parse-def state keymap key def kargs))
unless (eq bindable-def :ignore)
collect key
and collect (if general-implicit-kbd
(general--kbd bindable-def)
bindable-def)
and collect (general--normalize-extended-def def))))
;; * Helper Key Definers
(declare-function evil-define-minor-mode-key "evil-core")
(defun general-minor-mode-define-key (state mode key def _orig-def _kargs)
"A wrapper for `evil-define-minor-mode-key'."
(general-with-eval-after-load 'evil
(evil-define-minor-mode-key state mode key def)))
(declare-function lispy-define-key "lispy")
(defun general-lispy-define-key (_state keymap key def orig-def kargs)
"A wrapper for `lispy-define-key'."
(general-with-eval-after-load 'lispy
(let* ((keymap (general--get-keymap nil keymap))
(key (key-description key))
(plist (general--getf orig-def kargs :lispy-plist)))
(apply #'lispy-define-key keymap key def plist))))
(declare-function worf-define-key "worf")
(defun general-worf-define-key (_state keymap key def orig-def kargs)
"A wrapper for `worf-define-key'."
(general-with-eval-after-load 'worf
(let* ((keymap (general--get-keymap nil keymap))
(key (key-description key))
(plist (general--getf orig-def kargs :worf-plist)))
(apply #'worf-define-key keymap key def plist))))
(declare-function lpy-define-key "lpy")
(defun general-lpy-define-key (_state keymap key def _orig-def _kargs)
"A wrapper for `lpy-define-key'."
(general-with-eval-after-load 'lpy
(let* ((keymap (general--get-keymap nil keymap))
(key (key-description key)))
(lpy-define-key keymap key def))))
(declare-function evil-define-key* "evil-core")
(defun general--define-key-dispatch (state keymap maps kargs)
"In STATE (if non-nil) and KEYMAP, bind MAPS.
MAPS is composed of triplets of (key parsed-def original-def). This function
determines the appropriate base definer function to use based depending on
whether :definer is present in original-def or KARGS or whether STATE is
non-nil if no custom definer is specified."
(when (and general-override-auto-enable
(eq keymap 'general-override-mode-map)
(not general-override-mode))
(general-override-mode))
(while maps
(let* ((key (pop maps))
(def (pop maps))
(orig-def (pop maps))
(definer (general--getf orig-def kargs :definer)))
(if definer
(funcall (intern (format "general-%s-define-key"
(symbol-name definer)))
state keymap key def orig-def kargs)
(cond (state
;; just to get the symbol-value of the keymap when it is not
;; global/local
(setq keymap (general--get-keymap nil keymap nil t))
(general-with-eval-after-load 'evil
(evil-define-key* state keymap key def)))
(t
(setq keymap (general--get-keymap nil keymap))
(define-key keymap key def)))))))
(defvar general--definer-p nil
"Whether the current keybinding is being created with `general-define-key'.")
(defun general--define-key
(states keymap maps non-normal-maps global-maps kargs)
"A helper function for `general-define-key'.
Choose based on STATES and KEYMAP which of MAPS, NON-NORMAL-MAPS, and
GLOBAL-MAPS to use for the keybindings. This function will rewrite extended
definitions, add predicates when applicable, and then choose the base function
to bind the keys with by calling `general--define-key-dispatch'."
(let ((general--definer-p t))
(dolist (state states)
(let* ((non-normal-p (if state
(memq state general-non-normal-states)
(memq keymap
(mapcar #'general--evil-keymap-for-state
general-non-normal-states))))
(valid-maps (list (cond ((and non-normal-maps non-normal-p)
non-normal-maps)
((and global-maps non-normal-p)
nil)
(t
maps))
global-maps)))
(dolist (maps valid-maps)
(when maps
(setq maps (general--parse-maps state keymap maps kargs))
;; NOTE: :definer 'minor-mode cannot be specified locally
(general--record-keybindings keymap state maps
(eq (cl-getf kargs :definer)
'minor-mode))
(general--define-key-dispatch state keymap maps kargs)))))))
;; * Functions With Keyword Arguments
;;;###autoload
(cl-defun general-define-key
(&rest maps &key
definer
(states general-default-states)
(keymaps general-default-keymaps keymaps-specified-p)
(prefix general-default-prefix)
(non-normal-prefix general-default-non-normal-prefix)
(global-prefix general-default-global-prefix)
infix
prefix-command
prefix-map
prefix-name
predicate
;; related to extended definitions
package
properties
repeat
jump
major-modes
(wk-match-keys t)
(wk-match-binding t)
(wk-full-keys t)
;; for custom key definers
lispy-plist
worf-plist
&allow-other-keys)
"The primary key definition function provided by general.el.
Define MAPS, optionally using DEFINER, in the keymap(s) corresponding to STATES
and KEYMAPS.
MAPS consists of paired keys (vectors or strings; also see
`general-implicit-kbd') and definitions (those mentioned in `define-key''s
docstring and general.el's \"extended\" definitions). All pairs (when not
ignored) will be recorded and can be later displayed with
`general-describe-keybindings'.
If DEFINER is specified, a custom key definer will be used to bind MAPS. See
general.el's documentation/README for more information.
Unlike with normal key definitions functions, the keymaps in KEYMAPS should be
quoted (this allows using the keymap name for other purposes, e.g. deferring
keybindings if the keymap symbol is not bound, optionally inferring the
corresponding major mode for a symbol by removing \"-map\" for :which-key,
easily storing the keymap name for use with `general-describe-keybindings',
etc.). Note that general.el provides other key definer macros that do not
require quoting keymaps.
STATES corresponds to the evil state(s) to bind the keys in. Non-evil users
should not set STATES. When STATES is non-nil, `evil-define-key*' will be
used (the evil auxiliary keymaps corresponding STATES and KEYMAPS will be used);
otherwise `define-key' will be used (unless DEFINER is specified). KEYMAPS
defaults to 'global. There is also 'local, which create buffer-local
keybindings for both evil and non-evil keybindings. There are other special,
user-alterable \"shorthand\" symbols for keymaps and states (see
`general-keymap-aliases' and `general-state-aliases').
Note that STATES and KEYMAPS can either be lists or single symbols. If any
keymap does not exist, those keybindings will be deferred until the keymap does
exist, so using `eval-after-load' is not necessary with this function.
PREFIX corresponds to a key to prefix keys in MAPS with and defaults to none. To
bind/unbind a key specified with PREFIX, \"\" can be specified as a key in
MAPS (e.g. ...:prefix \"SPC\" \"\" nil... will unbind space).
The keywords in this paragraph are only useful for evil users. If
NON-NORMAL-PREFIX is specified, this prefix will be used instead of PREFIX for
states in `general-non-normal-states' (e.g. the emacs and insert states). This
argument will only have an effect if one of these states is in STATES or if
corresponding global keymap (e.g. `evil-insert-state-map') is in KEYMAPS.
Alternatively, GLOBAL-PREFIX can be used with PREFIX and/or NON-NORMAL-PREFIX to
bind keys in all states under the specified prefix. Like with NON-NORMAL-PREFIX,
GLOBAL-PREFIX will prevent PREFIX from applying to `general-non-normal-states'.
INFIX can be used to append a string to all of the specified prefixes. This is
potentially useful when you are using GLOBAL-PREFIX and/or NON-NORMAL-PREFIX so
that you can sandwich keys in between all the prefixes and the specified keys in
MAPS. This may be particularly useful if you are using default prefixes in a
wrapper function/macro so that you can add to them without needing to re-specify
all of them. If none of the other prefix keyword arguments are specified, INFIX
will have no effect.
If PREFIX-COMMAND or PREFIX-MAP is specified, a prefix command and/or keymap
will be created. PREFIX-NAME can be additionally specified to set the keymap
menu name/prompt. If PREFIX-COMMAND is specified, `define-prefix-command' will
be used. Otherwise, only a prefix keymap will be created. Previously created
prefix commands/keymaps will never be redefined/cleared. All prefixes (including
the INFIX key, if specified) will then be bound to PREFIX-COMMAND or PREFIX-MAP.
If the user did not specify any PREFIX or manually specify any KEYMAPS, general
will bind all MAPS in the prefix keymap corresponding to either PREFIX-MAP or
PREFIX-COMMAND instead of in the default keymap.
PREDICATE corresponds to a predicate to check to determine whether a definition
should be active (e.g. \":predicate '(eobp)\"). Definitions created with a
predicate will only be active when the predicate is true. When the predicate is
false, key lookup will continue to search for a match in lower-precedence
keymaps.
In addition to the normal definitions supported by `define-key', general.el also
provides \"extended\" definitions, which are plists containing the normal
definition as well as other keywords. For example, PREDICATE can be specified
globally or locally in an extended definition. New global (~general-define-key~)
and local (extended definition) keywords can be added by the user. See
`general-extended-def-keywords' and general.el's documentation/README for more
information.
PACKAGE is the global version of the extended definition keyword that specifies
the package a keymap is defined in (used for \"autoloading\" keymaps)
PROPERTIES, REPEAT, and JUMP are the global versions of the extended definition
keywords used for adding evil command properties to commands.
MAJOR-MODES, WK-MATCH-KEYS, WK-MATCH-BINDINGS, and WK-FULL-KEYS are the
corresponding global versions of which-key extended definition keywords. They
will only have an effect for extended definitions that specify :which-key or
:wk. See the section on extended definitions in the general.el
documentation/README for more information.
LISPY-PLIST and WORF-PLIST are the global versions of extended definition
keywords that are used for each corresponding custom DEFINER."
;; to silence compiler warning; variables that are later extracted from kargs
(ignore definer
predicate
package
properties
repeat
jump
major-modes
lispy-plist
worf-plist)
(let ((prefix-def (or prefix-command
(when prefix-map
(list :keymap prefix-map))))
non-normal-prefix-maps
global-prefix-maps
kargs)
;; don't force the user to wrap a single state or keymap in a list
(general--ensure-lists states keymaps)
;; unalias states and keymaps
(setq states (mapcar (lambda (state) (general--unalias state t))
states))
(setq keymaps (mapcar #'general--unalias keymaps))
;; remove keyword arguments from rest var
(let ((split-maps (general--remove-keyword-args maps)))
(setq maps (car split-maps)
;; order will be preserved; matters for duplicates
kargs (append
(list
;; should be included even if not manually specified
;; (because have non-nil defaults)
:wk-match-keys wk-match-keys
:wk-match-binding wk-match-binding
:wk-full-keys wk-full-keys
;; so :keymaps and :states are always lists in kargs
;; needed for matching against :major-modes
:keymaps keymaps
;; for consistency; may be useful in future or for user
:states states)
(cadr split-maps))))
(general--define-prefix prefix-command prefix-map prefix-name)
(when (and (or prefix-map prefix-command)
(not (or prefix keymaps-specified-p)))
(setq keymaps (list (or prefix-map prefix-command))))
;; TODO reduce code duplication here
(when non-normal-prefix
(setq non-normal-prefix-maps
(general--apply-prefix-and-kbd
(general--concat t non-normal-prefix infix)
(append (when (and prefix prefix-def)
(list "" prefix-def))
maps))))
(when global-prefix
(setq global-prefix-maps
(general--apply-prefix-and-kbd
(general--concat t global-prefix infix)
(append (when (and prefix prefix-def)
(list "" prefix-def))
maps))))
;; last so not applying prefix twice
(setq maps (general--apply-prefix-and-kbd
(general--concat t prefix infix)
(append (when (and prefix prefix-def)
(list "" prefix-def))
maps)))
(dolist (keymap keymaps)
(general--delay `(or (memq ',keymap '(local global))
(boundp ',keymap))
`(general--define-key ',states
',keymap
',maps
',non-normal-prefix-maps
',global-prefix-maps
',kargs)
'after-load-functions t nil
(symbol-name
(cl-gensym (format "general-define-key-in-%s" keymap)))))))
;;;###autoload
(defmacro general-emacs-define-key (keymaps &rest args)
"A wrapper for `general-define-key' that is similar to `define-key'.
It has a positional argument for KEYMAPS (that will not be overridden by a later
:keymaps argument). Besides this, it acts the same as `general-define-key', and
ARGS can contain keyword arguments in addition to keybindings. This can
basically act as a drop-in replacement for `define-key', and unlike with
`general-define-key', KEYMAPS does not need to be quoted."
(declare (indent 1))
`(general-define-key
:keymaps ,(if (and (listp keymaps)
(eq (car keymaps) 'quote))
`,keymaps
`',keymaps)
,@args))
;;;###autoload
(defmacro general-evil-define-key (states keymaps &rest args)
"A wrapper for `general-define-key' that is similar to `evil-define-key'.
It has positional arguments for STATES and KEYMAPS (that will not be overridden
by a later :keymaps or :states argument). Besides this, it acts the same as
`general-define-key', and ARGS can contain keyword arguments in addition to
keybindings. This can basically act as a drop-in replacement for
`evil-define-key', and unlike with `general-define-key', KEYMAPS does not need
to be quoted."
(declare (indent 2))
`(general-define-key
:states ,(if (and (listp states)
(eq (car states) 'quote))
`,states
`',states)
:keymaps ,(if (and (listp keymaps)
(eq (car keymaps) 'quote))
`,keymaps
`',keymaps)
,@args))
(defun general--positional-arg-p (arg)
"Return whether ARG is a positional argument for a key definer.
Keyword arguments and strings/vectors are not considered positional arguments."
(and arg
(or (symbolp arg) (listp arg))
(not (keywordp arg))))
;;;###autoload
(defmacro general-def (&rest args)
"General definer that takes a variable number of positional arguments in ARGS.
This macro will act as `general-define-key', `general-emacs-define-key', or
`general-evil-define-key' based on how many of the initial arguments do not
correspond to keybindings. All quoted and non-quoted lists and symbols before
the first string, vector, or keyword are considered to be positional arguments.
This means that you cannot use a function or variable for a key that starts
immediately after the positional arguments. If you need to do this, you should
use one of the definers that `general-def' dispatches to or explicitly separate
the positional arguments from the maps with a bogus keyword pair like
\":start-maps t\""
(declare (indent defun))
(let ((pos-args 0))
(while (general--positional-arg-p (nth pos-args args))
(cl-incf pos-args))
(cl-case pos-args
(0
`(general-define-key ,@args))
(1
`(general-emacs-define-key ,@args))
(2
`(general-evil-define-key ,@args)))))
;;;###autoload
(cl-defmacro general-create-definer (name &rest defaults &key wrapping
&allow-other-keys)
"A helper macro to create wrappers for `general-def'.
This can be used to create key definers that will use a certain keymap, evil
state, prefix key, etc. by default. NAME is the wrapper name and DEFAULTS are
the default arguments. WRAPPING can also be optionally specified to use a
different definer than `general-def'. It should not be quoted."
(declare (indent defun))
(let ((defaults (cl-loop for (key val) on defaults by 'cddr
unless (eq key :wrapping)
collect key
and collect val))
(definer (or wrapping 'general-def)))
`(defmacro ,name (&rest args)
(declare (indent defun))
,(let ((print-quoted t))
(format
"A wrapper for `%s'.
It has the following defaults:
%s"
definer defaults))
;; can still override keywords afterwards (first keyword takes precedence)
`(,',definer
,@args ,@',defaults))))
(defun general--starter-arg-p (arg)
"Return whether ARG is a keyword or positional argument for a key definer."
(or (keywordp arg)
(general--positional-arg-p arg)))
;;;###autoload
(defmacro general-defs (&rest args)
"A wrapper that splits into multiple `general-def's.
Each consecutive grouping of positional argument followed by keyword/argument
pairs (having only one or the other is fine) marks the start of a new section.
Each section corresponds to one use of `general-def'. This means that settings
only apply to the keybindings that directly follow."
(declare (indent defun)
(debug [&rest sexp]))
(let (arglists
arglist)
(while args
(while (and args (general--starter-arg-p (car args)))
(when (keywordp (car args))
(push (pop args) arglist))
(push (pop args) arglist))
(while (and args (not (general--starter-arg-p (car args))))
(push (pop args) arglist)
(push (pop args) arglist))
(push (nreverse arglist) arglists)
(setq arglist nil))
`(progn
,@(mapcar (lambda (arglist)
(cons 'general-def arglist))
(nreverse arglists)))))
;;;###autoload
(cl-defmacro general-unbind (&rest args)
"A wrapper for `general-def' to unbind multiple keys simultaneously.
Insert after all keys in ARGS before passing ARGS to `general-def.' \":with
#'func\" can optionally specified to use a custom function instead (e.g.
`ignore')."
(declare (indent defun))
;; Note: :with can be at an odd position, so must handle internally and not
;; with &key
(let* (with
(split-args (general--remove-keyword-args args))
(kargs (cl-loop for (key val) on (cadr split-args) by 'cddr
if (eq key :with)
do (setq with val)
else
collect key
and collect val))
(positional-args-and-maps
;; interleave appropriate definitions into maps
(cl-loop for key in (car split-args)
collect key
and
unless (general--positional-arg-p key)
collect (if (eq with t)
nil
with)))
(args (append positional-args-and-maps kargs)))
`(general-def ,@args)))
;; * Displaying Keybindings
(defun general--to-string (x)
"Convert key vector or symbol X to a string."
(cond ((vectorp x)
(key-description x))
((symbolp x)
(symbol-name x))
(t
x)))
;; these sorting functions assume x != y (which will hold true for
;; `general-keybindings')
(defun general--< (x y)
"Return t if X is alphabetically less than Y.
Each should be either a string, symbol, or vector. Nil is a special case and is
considered the \"smallest\"."
(cond ((null x)
t)
((null y)
nil)
(t
(setq x (general--to-string x)
y (general--to-string y))
(string< x y))))
(defun general-sort-by-car (list)
"Sort LIST by comparing the car of each element with `general--<'."
(cl-sort list #'general--< :key #'car))
(defun general-sort-by-cadr (list)
"Sort LIST by comparing the cadr of each element with `general--<'."
(cl-sort list #'general--< :key #'cadr))
(defvar general-describe-evil-states
'(nil
insert
emacs
hybrid
normal
visual
motion
operator
replace)
"Ordered list of evil states used for `general--evil-state-<'.")
(defun general--evil-state-< (x y)
"Return t if evil state X should come before state Y.
If X and Y are conses, the first element will be compared. Ordering is based on
`general-describe-evil-states' or the symbol names for states not in the list."
(let ((xind (cl-position x general-describe-evil-states))
(yind (cl-position y general-describe-evil-states)))
(cond ((and (null xind)
(null yind))
(general--< x y))
((null xind)
nil)
((null yind)
t)
(t
(< xind yind)))))
(defun general--sort-evil-state-conses (state-conses)
"Sort STATE-CONSES using `general--evil-state-<'."
(cl-sort state-conses #'general--evil-state-< :key #'car))
(defun general--print-map (map)
"Print the keybinding MAP."
(cl-destructuring-bind (key command previous) map
(princ (format "|=%.50s=|~%.50s~|~%.50s~|\n"
(replace-regexp-in-string "|" "¦" (key-description key))
command
previous))))
(defun general--print-maps-table (maps)
"Print an org table for MAPS."
(when general-describe-keybinding-sort-function
(setq maps (funcall general-describe-keybinding-sort-function maps)))
(princ "|key|command|previous|\n|-+-|\n")
(dolist (map maps)
(general--print-map map))
(princ "\n"))
(defun general--print-state-heading (state-cons)
"Print a table and possibly a heading for STATE-CONS."
(let ((state (car state-cons))
(maps (cdr state-cons)))
(unless (null state)
(princ (capitalize (concat "** " (symbol-name state) " State\n"))))
(general--print-maps-table maps)))
(defun general--print-keymap-heading (keymap-cons)
"Print headings and tables for KEYMAP-CONS."
(let ((keymap (car keymap-cons))
(state-conses (cdr keymap-cons)))
(princ (capitalize (concat "* " (symbol-name keymap) " Keybindings\n")))
(when general-describe-state-sort-function
(setq state-conses (funcall general-describe-state-sort-function
state-conses)))
(dolist (state-cons state-conses)
(general--print-state-heading state-cons))))
(declare-function org-at-heading-p "org")
(declare-function org-table-align "org-table")
(declare-function outline-next-heading "outline")
(defvar org-startup-folded)
;;;###autoload
(defun general-describe-keybindings (&optional arg)
"Show all keys that have been bound with general in an org buffer.
Any local keybindings will be shown first followed by global keybindings.
With a non-nil prefix ARG only show bindings in active maps."
(interactive "P")
(with-output-to-temp-buffer "*General Keybindings*"
(let* ((keybindings (append
(copy-alist general-keybindings)
(list (cons 'local general-local-keybindings))))
(active-maps (current-active-maps)))
;; print prioritized keymaps first (if any)
(dolist (keymap general-describe-priority-keymaps)
(let ((keymap-cons (assq keymap keybindings)))
(when (and keymap-cons
(or (null arg)
(and (boundp (car keymap-cons))
(memq (symbol-value (car keymap-cons))
active-maps))))
(general--print-keymap-heading keymap-cons)
(setq keybindings (assq-delete-all keymap keybindings)))))
;; sort the remaining and then print them
(when general-describe-keymap-sort-function
(setq keybindings (funcall general-describe-keymap-sort-function
keybindings)))
(dolist (keymap-cons keybindings)
(when (or (null arg)
(and (boundp (car keymap-cons))
(memq (symbol-value (car keymap-cons)) active-maps)))
(general--print-keymap-heading keymap-cons)))))
(with-current-buffer "*General Keybindings*"
(let ((org-startup-folded 'showall))
(org-mode))
(read-only-mode -1)
(while (progn
(while (progn
(forward-line)
(org-at-heading-p)))
(org-table-align)
(outline-next-heading)))
(goto-char (point-min))
(read-only-mode)))
;; * Functions/Macros to Aid Key Definition
;; ** Helpers
(cl-defun general--call-interactively
(function &optional (remap t) record-flag keys)
"Like `call-interactively' but use the remapped FUNCTION if it exists.
If REMAP is specified as nil (it is true by default), this is the same as
`call-interactively'. FUNCTION, RECORD-FLAG, and KEYS are passed to
`call-interactively'."
(when remap
(setq function (or (key-binding (kbd (format "<remap> <%s>" function)))
function)))
(call-interactively function record-flag keys))
;; ** Key Simulation
;; https://emacs.stackexchange.com/questions/6037/emacs-bind-key-to-prefix/13432#13432
;; altered to
;; - allow execution in an arbitrary state and keymap
;; - create a named function with a docstring
;; - optionally dynamically lookup the key(s) up in the correct keymap to try to
;; match a command to execute instead
;; - handle more edge cases like correctly working with macros/repeating
;; TODO
;; - rename keys arguments to key for consistency with builtin functions
(declare-function evil-change-state "evil-core")
(defvar evil-no-display)
(defvar evil-state)
(defvar evil-previous-state)
(defvar evil-previous-state-alist)
(defvar evil-next-state)
(defmacro general--save-state (&rest body)
"Save the current state; execute BODY; restore the state.
This is a combination of `evil-without-display' and `evil-save-state'. It is
necessary to define this directly in general so that it is available when
general is compiled (as evil is an optional dependency and may not be installed
when general is compiled)."
(declare (indent defun)
(debug t))
`(let* ((evil-no-display t)
(evil-state evil-state)
(evil-previous-state evil-previous-state)
(evil-previous-state-alist (copy-tree evil-previous-state-alist))
(evil-next-state evil-next-state)
(old-state evil-state)
(inhibit-quit t)
(buf (current-buffer)))
(unwind-protect
(progn ,@body)
(when (buffer-live-p buf)
(with-current-buffer buf
(evil-change-state old-state))))))
;;;###autoload
(cl-defmacro general-key (key &key
state
docstring
accept-default no-remap position)
"Act as KEY's definition in the current context.
This uses an extended menu item's capability of dynamically computing a
definition. It is recommended over `general-simulate-key' wherever possible. KEY
should be a string given in `kbd' notation and should correspond to a single
definition (as opposed to a sequence of commands). When STATE is specified, look
up KEY with STATE as the current evil state. When specified, DOCSTRING will be
the menu item's name/description. ACCEPT-DEFAULT, NO-REMAP, and POSITION are
passed to `key-binding'."
`'(menu-item
,(or docstring "")
nil
:filter
(lambda (&optional _)
,(if state
`(general--save-state
(evil-change-state ,state)
(key-binding (general--kbd ,key) ,accept-default ,no-remap
,position))
`(key-binding (general--kbd ,key) ,accept-default ,no-remap
,position)))))
(defvar general--last-simulated-command nil
"Holds the last simulated command (or nil for incomplete key sequence).")
(defvar general--simulate-next-as-is nil
"Whether to fake keys unconditionally in the next `general--simulate-keys'.
This is used for testing (but could potentially be useful for a user). Since
`general--simulate-keys' will normally assume it is being run inside a macro
that was manually recorded, this is needed when executing a keyboard macro that
ends up running `general--simulate-keys' for the first time.")
(defvar general--simulate-as-is nil
"Whether to fake the keys unconditionally in any `general--simulate-keys'.")
(defun general--key-binding (keys &optional state keymap)
"Look up KEYS in the keymap corresponding to STATE and/or KEYMAP.
Continually check whether subsequences of KEYS are bound to a command or keymap
starting with the full KEYS and ending when a match is found or no subsequences
remain. Unlike `lookup-key' if KEYS is not matched, fall back to checking with
`key-binding'. If STATE is specified and KEYMAP is not, temporarily switch to
STATE to look up the keys (this means that keybindings inherited from a
different evil state can still be detected). Return a list of the match and the
leftover keys (or nil if the full KEYS was matched)."
(let* ((keymap (when keymap
(general--get-keymap state keymap)))
(len (length keys))
(ind len)
match)
(while (and (> ind 0) (not match))
(let* ((key (substring keys 0 ind))
(result (cond (keymap
(or (lookup-key keymap key)
(key-binding key)))
(state
;; this also works fine when evil-local-mode is off
(general--save-state
(evil-change-state state)
(key-binding key)))
(t
(key-binding key)))))
(if (or (commandp result)
(keymapp result))
(setq match result)
(cl-decf ind))))
(list match
(if (= ind len)
nil
(substring keys ind len)))))
(cl-defun general--simulate-keys (command keys &optional state keymap
(lookup t)
(remap t))
"Simulate COMMAND followed by KEYS in STATE and/or KEYMAP.
If COMMAND is nil, just simulate KEYS. If STATE and KEYMAP are nil, simulate the
keys in the current context. When COMMAND is non-nil, STATE and KEYMAP will have
no effect. KEYS should be a string that can be passed to `kbd' or nil. If KEYS
is nil, the COMMAND will just be called interactively. If COMMAND is nil and
LOOKUP is non-nil, KEYS will be looked up in the correct context to determine if
any subsequence corresponds to a command or keymap. If a command is matched,
that command will be called followed by the simulation of any leftover keys. To
simulate the keys as-is without any lookup, LOOKUP can be explicitly specified
as nil. When COMMAND has been remapped (i.e. [remap COMMAND] is currently
bound), the remapped version will be used instead of the original command unless
REMAP is specified as nil (it is true by default)."
(let* ((keys (when keys
(general--kbd keys)))
;; TODO remove when get rid of `general-simulate-keys'
(state (if (eq state t)
'emacs
state)))
(unless (or command (not lookup))
(cl-destructuring-bind (match leftover-keys)
(general--key-binding keys state keymap)
(cond ((commandp match)
(setq command match
keys leftover-keys))
;; not documented because no current use case
;; left in because may be useful later
((and (eq lookup 'always) (keymapp match))
(setq keymap match
state nil
;; should be nil
keys leftover-keys)))))
;; set context for keys
(when (and keymap (not command))
;; TODO is it possible to set transient map and then use e.g.
;; `evil-execute-in-normal-state' (so that commands bound in the motion
;; state auxiliary map could also be executed)?
(set-transient-map (general--get-keymap state keymap)))
(when keys
;; only set prefix-arg when only keys
;; (otherwise will also affect the next command)
(unless command
(setq prefix-arg current-prefix-arg))
(when (or general--simulate-as-is
general--simulate-next-as-is
(not executing-kbd-macro))
(setq general--simulate-next-as-is nil)
;; keys are incorrectly saved as this-command-keys when recording macros
;; these keys will be played back, so don't simulate them
(setq unread-command-events
(nconc
;; force keys to be added to this-command-keys
;; this happens normally already for macros but it needs to be
;; forced for evil-repeat though, which will only include the
;; first key otherwise (ideally no keys would ever be added in
;; either case)
(mapcar (lambda (ev) (cons t ev))
(listify-key-sequence keys))
unread-command-events))))
(when command
(let ((this-command command))
(general--call-interactively command remap)))
(setq general--last-simulated-command command)))
;;;###autoload
(cl-defmacro general-simulate-keys (keys &optional state keymap
(lookup t)
docstring name)
"Deprecated. Please use `general-simulate-key' instead."
(let* ((command (when (listp keys)
(car keys)))
(keys (if (listp keys)
(cadr keys)
keys))
(state (if (eq state t)
''emacs
state))
(name (or name
(intern (concat
(format "general-simulate-%s"
(if command
(eval command)
""))
(when command
"-")
(replace-regexp-in-string " " "_" keys)
(when state
(concat "-in-"
(symbol-name (eval state))
"-state"))
(when keymap
(concat "-in-"
(symbol-name keymap))))))))
`(progn
(eval-after-load 'evil
'(evil-set-command-property #',name :repeat 'general--simulate-repeat))
(defun ,name
()
,(or docstring
(concat "Simulate "
(when command
(concat "`"
(symbol-name (eval command))
"' then "))
"'"
keys
"' in "
(cond ((and state keymap)
(concat (symbol-name (eval state))
" state in `"
(symbol-name keymap)
"'."))
(keymap
(concat (symbol-name keymap)
"."))
(state
(concat (symbol-name (eval state))
" state."))
(t
"the current context."))))
(interactive)
(general--simulate-keys ,command ,keys ,state ,keymap ,lookup)))))
(make-obsolete 'general-simulate-keys 'general-simulate-key "2018-01-14")
;;;###autoload
(cl-defmacro general-simulate-key (keys
&key
state keymap
name docstring
(lookup t)
which-key
(remap t))
"Create and return a command that simulates KEYS in STATE and KEYMAP.
KEYS should be a string given in `kbd' notation. It can also be a list of a
single command followed by a string of the key(s) to simulate after calling that
command. STATE should only be specified by evil users and should be a quoted
evil state. KEYMAP should not be quoted. Both STATE and KEYMAP aliases are
supported (but they have to be set when the macro is expanded). When neither
STATE or KEYMAP are specified, the key(s) will be simulated in the current
context.
If NAME is specified, it will replace the automatically generated function name.
NAME should not be quoted. If DOCSTRING is specified, it will replace the
automatically generated docstring.
Normally the generated function will look up KEY in the correct context to try
to match a command. To prevent this lookup, LOOKUP can be specified as nil.
Generally, you will want to keep LOOKUP non-nil because this will allow checking
the evil repeat property of matched commands to determine whether or not they
should be recorded. See the docstring for `general--simulate-keys' for more
information about LOOKUP.
When a WHICH-KEY description is specified, it will replace the command name in
the which-key popup.
When a command name is specified and that command has been remapped (i.e. [remap
command] is currently bound), the remapped version will be used instead of the
original command unless REMAP is specified as nil (it is true by default).
The advantages of this over a keyboard macro are as follows:
- Prefix arguments are supported
- The user can control the context in which the keys are simulated
- The user can simulate both a named command and keys
- The user can simulate an incomplete key sequence (e.g. for a keymap)"
(declare (indent defun))
(let* ((command (when (listp keys)
(car keys)))
(keys (if (listp keys)
(cadr keys)
keys))
(state (general--unalias (eval state) t))
(keymap (general--unalias keymap))
(name (or name
(intern (concat
(format "general-simulate-%s"
(if command
(eval command)
""))
(when command
"-")
(replace-regexp-in-string " " "_" keys)
(when state
(concat "-in-"
(symbol-name state)
"-state"))
(when keymap
(concat "-in-"
(symbol-name keymap))))))))
`(progn
(eval-after-load 'evil
'(evil-set-command-property #',name :repeat 'general--simulate-repeat))
(when ,which-key
(general-with-eval-after-load 'which-key
(push '((nil . ,(symbol-name name))
nil . ,which-key)
which-key-replacement-alist)))
(defun ,name
()
,(or docstring
(concat "Simulate "
(when command
(concat "`"
(symbol-name (eval command))
"' then "))
"'"
keys
"' in "
(cond ((and state keymap)
(concat (symbol-name state)
" state in `"
(symbol-name keymap)
"'."))
(keymap
(concat (symbol-name keymap)
"."))
(state
(concat (symbol-name state)
" state."))
(t
"the current context."))))
(interactive)
(general--simulate-keys ,command ,keys ',state ,keymap ,lookup ,remap))
#',name)))
(defun general--repeat-abort-p (repeat-prop)
"Return t if repeat recording should be aborted based on REPEAT-PROP."
(or (memq repeat-prop (list nil 'abort 'ignore))
(and (eq repeat-prop 'motion)
(not (memq evil-state '(insert replace))))))
(declare-function evil-repeat-record "evil-repeat")
(declare-function evil-get-command-property "evil-common")
(declare-function evil-repeat-abort "evil-repeat")
(declare-function evil-this-command-keys "evil-repeat")
(declare-function evil-clear-command-keys "evil-repeat")
(defvar evil-this-register)
(defun general--simulate-repeat (flag)
"Modified version of `evil-repeat-keystrokes'.
It behaves as normal but will check the repeat property of a simulated command
to determine whether to abort recording."
(cond ((eq flag 'pre)
(when evil-this-register
(evil-repeat-record
`(set evil-this-register ,evil-this-register))))
((eq flag 'post)
(let* ((command general--last-simulated-command)
(repeat-prop (evil-get-command-property command :repeat t)))
(if (and command (general--repeat-abort-p repeat-prop))
(evil-repeat-abort)
(evil-repeat-record
(evil-this-command-keys t))
(evil-clear-command-keys))))))
;; ** Key Dispatch
(defvar general--last-dispatch-command nil
"Holds the last command run from a `general-key-dispatch' function.")
(defun general--extend-key-sequence (keys)
"Read a key and append it to KEYS.
KEYS should be a string given in `kbd' notation."
(let ((key (read-event)))
(concat keys
(when keys
" ")
(key-description (if (characterp key)
(char-to-string key)
(vector key))))))
;;;###autoload
(cl-defmacro general-key-dispatch
(fallback-command &rest maps
&key
timeout
inherit-keymap
name docstring
which-key
(remap t)
&allow-other-keys)
"Create and return a command that runs FALLBACK-COMMAND or a command in MAPS.
MAPS consists of <key> <command> pairs. If a key in MAPS is matched, the
corresponding command will be run. Otherwise FALLBACK-COMMAND will be run with
the unmatched keys. So, for example, if \"ab\" was pressed, and \"ab\" is not
one of the key sequences from MAPS, the FALLBACK-COMMAND will be run followed by
the simulated keypresses of \"ab\". Prefix arguments will still work regardless
of which command is run. This is useful for binding under non-prefix keys. For
example, this can be used to redefine a sequence like \"cw\" or \"cow\" in evil
but still have \"c\" work as `evil-change'. If TIMEOUT is specified,
FALLBACK-COMMAND will also be run in the case that the user does not press the
next key within the TIMEOUT (e.g. 0.5).
NAME and DOCSTRING are optional keyword arguments. They can be used to replace
the automatically generated name and docstring for the created function. By
default, `cl-gensym' is used to prevent name clashes (e.g. allows the user to
create multiple different commands using `self-insert-command' as the
FALLBACK-COMMAND without explicitly specifying NAME to manually prevent
clashes).
When INHERIT-KEYMAP is specified, all the keybindings from that keymap will be
inherited in MAPS.
When a WHICH-KEY description is specified, it will replace the command name in
the which-key popup.
When command to be executed has been remapped (i.e. [remap command] is currently
bound), the remapped version will be used instead of the original command unless
REMAP is specified as nil (it is true by default)."
(declare (indent 1))
(let ((name (or name (cl-gensym (format "general-dispatch-%s-"
(eval fallback-command)))))
;; remove keyword arguments from maps
(maps (car (general--remove-keyword-args maps))))
`(progn
(eval-after-load 'evil
'(evil-set-command-property #',name :repeat 'general--dispatch-repeat))
(when ,which-key
(general-with-eval-after-load 'which-key
(push '((nil . ,(symbol-name name))
nil . ,which-key)
which-key-replacement-alist)))
;; TODO list all of the bound keys in the docstring
(defun ,name ()
,(or docstring (format (concat "Run %s or something else based"
"on the next keypresses.")
(eval fallback-command)))
(interactive)
(let ((map (make-sparse-keymap))
(maps (list ,@maps))
(invoked-keys (this-command-keys))
(timeout ,timeout)
(inherit-keymap ,inherit-keymap)
matched-command
fallback
char
timed-out-p)
(when inherit-keymap
(set-keymap-parent map inherit-keymap))
(while maps
(define-key map (general--kbd (pop maps)) (pop maps)))
(while (progn
(if timeout
(with-timeout (timeout (setq timed-out-p t))
;; TODO rename char
(setq char (general--extend-key-sequence char)))
(setq char (general--extend-key-sequence char)))
(and (not timed-out-p)
(keymapp (lookup-key map (kbd char))))))
(cond
((and (not timed-out-p)
(setq matched-command (lookup-key map (kbd char))))
;; necessary for evil-this-operator checks because
;; evil-define-operator sets evil-this-operator to this-command
(let ((this-command matched-command))
(general--call-interactively matched-command ,remap)))
(t
(setq matched-command ,fallback-command)
(general--simulate-keys ,fallback-command char
nil nil t ,remap)))
(setq general--last-dispatch-command matched-command)))
#',name)))
(defun general--dispatch-repeat (flag)
"Modified version of `evil-repeat-keystrokes'.
It behaves as normal but will check the repeat property of a simulated command
to determine whether to abort recording."
(cond ((eq flag 'pre)
(when evil-this-register
(evil-repeat-record
`(set evil-this-register ,evil-this-register))))
((eq flag 'post)
(let ((repeat-prop (evil-get-command-property
general--last-dispatch-command
:repeat t)))
(if (general--repeat-abort-p repeat-prop)
(evil-repeat-abort)
(evil-repeat-record (evil-this-command-keys t))
(evil-clear-command-keys))))))
;; ** Predicate Dispatch
;;;###autoload
(cl-defmacro general-predicate-dispatch
(fallback-def &rest defs
&key docstring
&allow-other-keys)
(declare (indent 1))
"Create a menu item that will run FALLBACK-DEF or a definition from DEFS.
DEFS consists of <predicate> <definition> pairs. Binding this menu-item to a key
will cause that key to act as the corresponding definition (a command, keymap,
etc) for the first matched predicate. If no predicate is matched FALLBACK-DEF
will be run. When FALLBACK-DEF is nil and no predicates are matched, the keymap
with the next highest precedence for the pressed key will be checked. DOCSTRING
can be specified as a description for the menu item."
;; remove keyword arguments from defs and sort defs into pairs
(let ((defs (cl-loop for (key value) on defs by 'cddr
unless (keywordp key)
collect (list key value))))
`'(menu-item
,(or docstring "") nil
:filter (lambda (&optional _)
(cond ,@(mapcar (lambda (pred-def)
`(,(car pred-def) ,(cadr pred-def)))
defs)
(t ,fallback-def))))))
;; ** Key "Translation"
;;;###autoload
(cl-defun general-translate-key (states keymaps
&rest maps
&key destructive
&allow-other-keys)
"Translate keys in the keymap(s) corresponding to STATES and KEYMAPS.
STATES should be the name of an evil state, a list of states, or nil. KEYMAPS
should be a symbol corresponding to the keymap to make the translations in or a
list of keymap names. Keymap and state aliases are supported (as well as 'local
and 'global for KEYMAPS). MAPS corresponds to a list of translations (key
replacement pairs). For example, specifying \"a\" \"b\" will bind \"a\" to
\"b\"'s definition in the keymap. If DESTRUCTIVE is non-nil, the keymap will be
destructively altered without creating a backup. If DESTRUCTIVE is nil, a backup
of the keymap will be stored on the initial invocation, and future invocations
will always look up keys in the backup keymap. On the other hand, if DESTRUCTIVE
is non-nil, calling this function multiple times with \"a\" \"b\" \"b\" \"a\",
for example, would continue to swap and unswap the definitions of these keys.
This means that when DESTRUCTIVE is non-nil, all related swaps/cycles should be
done in the same invocation."
(declare (indent defun))
(general--ensure-lists states keymaps)
(dolist (keymap-name keymaps)
(dolist (state states)
(setq keymap-name (general--unalias keymap-name)
state (general--unalias state t))
(let* ((keymap (general--get-keymap state keymap-name))
(backup-keymap (intern (format "general-%s%s-backup-map"
keymap-name
(if state
(format "-%s-state" state)
""))))
(lookup-keymap (if (and (not destructive)
(boundp backup-keymap))
(symbol-value backup-keymap)
(copy-keymap keymap)))
(maps (cl-loop for (key replacement) on maps by 'cddr
;; :destructive can be in MAPS
unless (keywordp key)
collect (general--kbd key)
and collect (lookup-key
lookup-keymap
(general--kbd replacement)))))
(unless (or destructive
(boundp backup-keymap))
(set backup-keymap lookup-keymap))
(apply #'general-define-key :states state :keymaps keymap-name maps)))))
;;;###autoload
(defmacro general-swap-key (states keymaps &rest args)
"Wrapper around `general-translate-key' for swapping keys.
STATES, KEYMAPS, and ARGS are passed to `general-translate-key'. ARGS should
consist of key swaps (e.g. \"a\" \"b\" is equivalent to \"a\" \"b\" \"b\" \"a\"
with `general-translate-key') and optionally keyword arguments for
`general-translate-key'."
(declare (indent defun))
(setq args (cl-loop for (key replacement) on args by 'cddr
collect key and collect replacement
and unless (keywordp key)
collect replacement and collect key))
`(general-translate-key ,states ,keymaps ,@args))
;; ** Automatic Key Unbinding
(defun general-unbind-non-prefix-key (define-key keymap key def)
"Use DEFINE-KEY in KEYMAP to unbind an existing non-prefix subsequence of KEY.
When a general key definer is in use and a subsequnece of KEY is already bound
in KEYMAP, unbind it using DEFINE-KEY. Always bind KEY to DEF using DEFINE-KEY."
(when general--definer-p
(let ((key (if (stringp key)
(string-to-vector key)
key)))
(while (numberp (lookup-key keymap key))
(setq key (cl-subseq key 0 -1)))
(funcall define-key keymap key nil)))
(funcall define-key keymap key def))
;;;###autoload
(defun general-auto-unbind-keys (&optional undo)
"Advise `define-key' to automatically unbind keys when necessary.
This will prevent errors when a sub-sequence of a key is already bound (e.g. the
user attempts to bind \"SPC a\" when \"SPC\" is bound, resulting in a \"Key
sequnce starts with non-prefix key\" error). When UNDO is non-nil, remove
advice."
(if undo
;; using general versions in case start recording advice for later display
(general-advice-remove 'define-key #'general-unbind-non-prefix-key)
(general-advice-add 'define-key :around #'general-unbind-non-prefix-key)))
;; ** Interactive Lambdas
(defmacro general-lambda (&rest body)
"Wrap BODY in an interactive lamba"
`(lambda () (interactive)
,@body))
(defalias 'general-l #'general-lambda)
;; * Functions/Macros to Aid Other Configuration
;; ** Settings
(defmacro general-setq (&rest settings)
"A stripped-down `customize-set-variable' with the syntax of `setq'.
Like `setq', multiple variables can be set at once; SETTINGS should consist of
variable value pairs. Some variables have a custom setter (specified with
`defcustom' and :set) that is used to run code necessary for changes to take
effect (e.g. `auto-revert-interval'). If a package has already been loaded, and
the user uses `setq' to set one of these variables, the :set code will not
run (e.g. in the case of `auto-revert-interval', the timer will not be updated).
Like with `customize-set-variable', `general-setq' will use the custom :set
setter when it exists. If the package defining the variable has not yet been
loaded, the custom setter will not be known, but it will still be run upon
loading the package. Unlike `customize-set-variable', `general-setq' does not
attempt to load any dependencies for the variable and does not support giving
variables comments. It also falls back to `set' instead of `set-default', so
that like `setq' it will change the local value of a buffer-local variable
instead of the default value."
`(progn
,@(cl-loop for (var val) on settings by 'cddr
collect `(funcall (or (get ',var 'custom-set) #'set)
',var ,val))))
;; ** Hooks
;;;###autoload
(defun general-add-hook (hooks functions &optional append local)
"A drop-in replacement for `add-hook'.
Unlike `add-hook', HOOKS and FUNCTIONS can be single items or lists. APPEND and
LOCAL are passed directly to `add-hook'."
(general--ensure-lists hooks functions)
(dolist (hook hooks)
(dolist (func functions)
(add-hook hook func append local))))
;;;###autoload
(defun general-remove-hook (hooks functions &optional local)
"A drop-in replacement for `remove-hook'.
Unlike `remove-hook', HOOKS and FUNCTIONS can be single items or lists. LOCAL is
passed directly to `remove-hook'."
(general--ensure-lists hooks functions)
(dolist (hook hooks)
(dolist (func functions)
(remove-hook hook func local))))
;; ** Advice
;;;###autoload
(defun general-advice-add (symbols where functions &optional props)
"A drop-in replacement for `advice-add'.
SYMBOLS, WHERE, FUNCTIONS, and PROPS correspond to the arguments for
`advice-add'. Unlike `advice-add', SYMBOLS and FUNCTIONS can be single items or
lists."
(general--ensure-lists symbols functions)
(dolist (symbol symbols)
(dolist (func functions)
(advice-add symbol where func props))))
;; specify full autoload to prevent function indirection (autoload generation
;; will put a /flipped/ defalias into the autoloads file causing an infinite
;; loop)
;;;###autoload (autoload 'general-add-advice "general")
(defalias 'general-add-advice #'general-advice-add)
;;;###autoload
(defun general-advice-remove (symbols functions)
"A drop-in replacement for `advice-remove'.
Unlike `advice-remove', SYMBOLS and FUNCTIONS can be single items or lists."
(general--ensure-lists symbols functions)
(dolist (symbol symbols)
(dolist (func functions)
(advice-remove symbol func))))
;;;###autoload (autoload 'general-remove-advice "general")
(defalias 'general-remove-advice #'general-advice-remove)
;; * Optional Setup
;;;###autoload
(defun general-evil-setup (&optional short-names _)
"Set up some basic equivalents for vim mapping functions.
This creates global key definition functions for the evil states.
Specifying SHORT-NAMES as non-nil will create non-prefixed function
aliases such as `nmap' for `general-nmap'."
(general-create-definer general-imap :states 'insert)
(general-create-definer general-emap :states 'emacs)
(general-create-definer general-nmap :states 'normal)
(general-create-definer general-vmap :states 'visual)
(general-create-definer general-mmap :states 'motion)
(general-create-definer general-omap :states 'operator)
(general-create-definer general-rmap :states 'replace)
(general-create-definer general-iemap :states '(insert emacs))
(general-create-definer general-nvmap :states '(normal visual))
;; these two don't have corresponding states
(general-create-definer general-itomap :keymaps 'evil-inner-text-objects-map)
(general-create-definer general-otomap :keymaps 'evil-outer-text-objects-map)
(general-create-definer general-tomap
:keymaps '(evil-outer-text-objects-map
evil-inner-text-objects-map))
(when short-names
(defalias 'imap #'general-imap)
(defalias 'emap #'general-emap)
(defalias 'nmap #'general-nmap)
(defalias 'vmap #'general-vmap)
(defalias 'mmap #'general-mmap)
(defalias 'omap #'general-omap)
(defalias 'rmap #'general-rmap)
(defalias 'iemap #'general-iemap)
(defalias 'nvmap #'general-nvmap)
(defalias 'itomap #'general-itomap)
(defalias 'otomap #'general-otomap)
(defalias 'tomap #'general-tomap)))
;; * Use-package Integration
;; maybe useful for something else in future
(defun general--extract-autoloadable-symbol (def)
"Extract an autoloadable symbol from DEF, a normal or extended definition.
This will also correctly extract the definition from a cons of the form (STRING
. DEFN). If the extracted definition is nil, a string, a lambda, a keymap symbol
from an extended definition, or some other definition that cannot be autoloaded,
return nil."
;; explicit null checks not required because nil return value means no def
(when (general--extended-def-p def)
;; extract definition
(let ((first (car def)))
(setq def (if (keywordp first)
(plist-get def :def)
first))))
(cond ((symbolp def)
def)
((and (consp def)
(symbolp (cdr def)))
(cdr def))))
(general-with-eval-after-load 'use-package-core
(declare-function use-package-concat "use-package")
(declare-function use-package-process-keywords "use-package")
(defvar use-package-keywords)
(defvar use-package-deferring-keywords)
;; ** :general Keyword
(setq use-package-keywords
;; should go in the same location as :bind
;; adding to end may not cause problems, but see issue #22
(cl-loop for item in use-package-keywords
if (eq item :bind-keymap*)
collect :bind-keymap* and collect :general
else
;; don't add duplicates
unless (eq item :general)
collect item))
;; altered args will be passed to the autoloads and handler functions
(defun use-package-normalize/:general (_name _keyword general-arglists)
"Return a plist containing the original ARGLISTS and autoloadable symbols."
(let* ((sanitized-arglist
;; combine arglists into one without function names or
;; positional arguments
(let (result)
(dolist (arglist general-arglists result)
(while (general--positional-arg-p (car arglist))
(setq arglist (cdr arglist)))
(setq result (append result arglist)))))
(commands
(cl-loop for (key def) on sanitized-arglist by 'cddr
when (and (not (keywordp key))
(not (null def))
(ignore-errors
;; TODO use cdr instead if possible
(setq def (eval def))
(setq def (general--extract-autoloadable-symbol
def))))
collect def)))
(list :arglists general-arglists :commands commands)))
(defun use-package-autoloads/:general (_name _keyword args)
"Return an alist of commands extracted from ARGS.
Return something like '((some-command-to-autoload . command) ...)."
(mapcar (lambda (command) (cons command 'command))
(plist-get args :commands)))
(defun use-package-handler/:general (name _keyword args rest state)
"Use-package handler for :general."
(use-package-concat
(use-package-process-keywords name rest state)
`(,@(mapcar (lambda (arglist)
;; Note: prefix commands are not valid functions
(if (or (functionp (car arglist))
(macrop (car arglist)))
`(,@arglist :package ',name)
`(general-def
,@arglist
:package ',name)))
(plist-get args :arglists)))))
;; ** :ghook and :gfhook Keyword
(setq use-package-keywords
;; should go in the same location as :bind
;; adding to end may not cause problems, but see issue #22
(cl-loop for item in use-package-keywords
if (eq item :hook)
collect :hook and collect :ghook and collect :gfhook
else
;; don't add duplicates
unless (memq item '(:ghook :gfhook))
collect item))
(defun general-normalize-hook-arglist (arglist mode-enable mode-hook
&optional symbol-is-function-p)
"Rewrite a :g(f)hook ARGLIST to a `general-add-hook' arglist.
MODE-ENABLE is the inferred command to enable the package's mode, and MODE-HOOK
is the mode inferred hook to enable the package's mode. When ARGLIST is a symbol
instead of a list, it will be considered to be a hook name unless
SYMBOL-IS-FUNCTION-P is non-nil, in which case it will considered to be a
function."
(cond ((ignore-errors (memq (car arglist) (list 'quote 'function)))
;; user passed in a hook or function; leave as is
(if symbol-is-function-p
;; '<package>-mode-hook <user specified function>
`(',mode-hook ,arglist)
;; <user specified hook> #'<package>-mode
`(,arglist ',mode-enable)))
((symbolp arglist)
;; user passed in an unquoted symbol (variable); don't quote it
(if symbol-is-function-p
`(',mode-hook ,arglist)
`(,arglist ',mode-enable)))
(t
;; actual list for `general-add-hook'
(if (= (length arglist) 1)
;; <user specified hook(s)> #'<package>-mode
`(,(car arglist) ',mode-enable)
(let ((hooks (car arglist))
(functions (cadr arglist)))
(when (or (null hooks)
(not (or (symbolp hooks)
(listp hooks))))
(setq hooks `',mode-hook))
(when (or (null functions)
(not (or (symbolp functions)
(listp functions))))
(setq functions `',mode-enable))
;; (cons hooks (cons functions (cddr arglist)))
`(,hooks ,functions ,@(cddr arglist)))))))
;; altered args will be passed to the autoloads and handler functions
(defun general-normalize-hook (name _keyword args &optional gfhookp)
"Return a plist containing arglists and autoloadable commands.
Transform ARGS into arglists suitable for `general-add-hook'."
(let* ((mode (if (string-match-p "mode\\'" (symbol-name name))
name
(intern (format "%s-mode" name))))
(mode-hook (intern (format "%s-hook" mode))))
(cl-loop for arg in args
collect (general-normalize-hook-arglist
arg mode mode-hook gfhookp))))
(defalias 'use-package-normalize/:ghook #'general-normalize-hook)
(defun use-package-autoloads/:ghook (_name _keyword arglists)
"Return an alist of commands extracted from ARGLISTS.
Return somethin"
(let (functions)
(dolist (arglist arglists)
(let ((function-position (cadr arglist)))
(cond ((not (listp function-position))
;; (not (ignore-errors (car function-position)))
;; ignore variables
)
((and (memq (car function-position) (list 'quote 'function))
(not (listp (cadr function-position))))
(push (cons (cadr function-position) 'command) functions))
((eq (car function-position) 'list)
(dolist (func (cdr function-position))
;; ignore variables and function/macro calls
(when (and (listp func)
(memq (car func) (list 'quote 'function)))
(push (cons (cadr func) 'command) functions))))
(t
(dolist (func (cadr function-position))
(push (cons func 'command) functions))))))
functions))
(defun use-package-handler/:ghook (name _keyword arglists rest state)
"Use-package handler for :ghook and :gfhook."
(use-package-concat
(use-package-process-keywords name rest state)
`(,@(mapcar (lambda (arglist)
arglist
`(general-add-hook ,@arglist))
arglists))))
(defun use-package-normalize/:gfhook (name keyword args)
"Use-package normalizer for :gfhook."
(general-normalize-hook name keyword args t))
(defalias 'use-package-handler/:gfhook #'use-package-handler/:ghook))
;; * Key-chord "Integration"
(defun general-chord (keys)
"Rewrite the string KEYS into a valid key-chord vector."
;; taken straight from key-chord.el
(if (/= 2 (length keys))
(error "Key-chord keys must have two elements"))
;; Exotic chars in a string are >255 but define-key wants 128..255 for those
(let ((key1 (logand 255 (aref keys 0)))
(key2 (logand 255 (aref keys 1))))
(vector 'key-chord key1 key2)))
(provide 'general)
;;; general.el ends here
|