-
Notifications
You must be signed in to change notification settings - Fork 21
/
objed.el
4229 lines (3599 loc) · 141 KB
/
objed.el
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
;;; objed.el --- Navigate and edit text objects. -*- lexical-binding: t -*-
;; Copyright (C) 2018-2019 Free Software Foundation, Inc.
;; Author: Clemens Radermacher <clemera@posteo.net>
;; Package-Requires: ((emacs "25") (cl-lib "0.5"))
;; Version: 0.8.3
;; Keywords: convenience
;; URL: https://github.com/clemera/objed
;; 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:
;;
;; A global minor-mode to navigate and edit text objects. Objed enables modal
;; editing and composition of commands, too. It combines ideas of other
;; Editors like Vim or Kakoune and tries to align them with regular Emacs
;; conventions.
;;
;; For more information also see:
;;
;; - My Blog: https://www.with-emacs.com/categories/objed/
;; - Project Readme: https://github.com/clemera/objed/blob/master/README.asc
;; - Project News: https://github.com/clemera/objed/blob/master/News.asc.
;;
;; Text objects are textual patterns like a line, a top level definition, a
;; word, a sentence or any other unit of text. When `objed-mode' is enabled,
;; certain editing commands (configurable) will activate `objed' and enable
;; its modal editing features. When active, keys which would usually insert a
;; character are mapped to objed commands. Other keys and commands will
;; continue to work as they normally would and exit this editing state again.
;;
;; By default important self inserting keys like Space or Return are not bound
;; to modal commands and will exit `objed' on insertion. This makes it
;; convenient to move around and continue adding new text.
;;
;; With activation `objed' shows the current object type in the mode-line. The
;; textual content of the object is highlighted visually in the buffer and the
;; cursor color is changed, too. The user can now navigate by units of this
;; object, change the object state or switch to other object types.
;;
;; The object state is either "inner" or "whole" and is indicated in the
;; modeline by (i) or (w) after the object type. With inner state, anything
;; that would be considered delimiters or padding around an object is
;; excluded.
;;
;; The user can apply operations to objects. By marking objects before
;; applying an operation, s?he can even operate on multiple objects at once.
;; This works similar to the way you interact with files in `dired'. When
;; marking an object the point moves on to the next object of this type.
;;
;; The object type used for initialization is determined by the mapping of the
;; entry command (see `objed-cmd-alist'). For example using
;; `beginning-of-defun' will activate `objed' using the `defun' object as
;; initial object type. With command `next-line', `objed' would initialize
;; with the `line' object.
;;
;; Objeds modal state provides basic movement commands which move by line,
;; word or character. Those switch automatically to the corresponding object
;; type, otherwise they work the same as the regular Emacs movement commands.
;; Other commands only activate the part between the initial position and the
;; new position moved to. By repeating commands you can often expand/proceed
;; to other objects. This way you can compose movement and editing operations
;; very efficiently.
;;
;; The expansion commands distinguish between block objects (objects built out
;; of lines of text) and context objects (programming constructs like strings,
;; brackets or textual components like sentences). This way you can quickly
;; expand to the desired objects.
;;
;; For example to move to the end of the paragraph, the user would first move
;; to the end of the line with "e". This would activate the text between the
;; starting position and the end of the line. The user can now continue to the
;; end of the paragraph by by pressing "e" again. Now s?he is able to proceed
;; even further by pressing "e" again OR to continue by adding new text to the
;; end of the paragraph OR to continue by acting on the text moved over, for
;; example killing it by pressing "k".
;;
;; As often with text editing, the explanation sounds more complicated than
;; using it. To get a better impression of the editing workflow with `objed'
;; have look at https://github.com/clemera/objed where you can find some
;; animated demos.
;;
;; To learn more about available features and commands have a look at the
;; descriptions below or the Docstrings and bindings defined in `objed-map'.
;; To define your own operations and text objects see `objed-define-op' and
;; `objed-define-object'.
;;
;; Although some features are still experimental the basic user interface will
;; stay the same.
;;
;;
;; CONTRIBUTE:
;;
;; I'm happy to receive pull requests or ideas to improve this package. Some
;; parts suffer from the bottom up approach of developing it, but this also
;; allowed me to experiment a lot and try ideas while working on them,
;; something that Emacs is especially good at. Most of the features are tested
;; using `emacs-lisp-mode' but hopefully there aren't to many problems using
;; modes for other languages, I tried my best to write text objects in a
;; language agnostic way. Testing this and writing more tests in general would
;; be an important next step.
;;
;; This package would never been possible without the helpful community around
;; Emacs. Thank you all and see you in parendise...Share the software!
;;
;;
;;; Code:
;; * Deps
(require 'cl-lib)
(require 'nadvice)
(require 'face-remap)
(require 'subword)
(require 'hl-line)
(require 'objed-objects)
;; * Customization
(defgroup objed nil
"Navigate and edit text objects."
:group 'convenience
:prefix "objed-")
(defgroup objed-faces nil
"Faces for `objed'"
:group 'objed
:group 'faces)
;; * Faces
(defface objed-hl
'((t (:inherit highlight)))
"Face used for highlighting textual content of current object."
:group 'objed-faces)
(defface objed-mark
'((t (:inherit region)))
"Face used for marked objects."
:group 'objed-faces)
(defface objed-mode-line
'((t (:inherit mode-line-inactive)))
"Face used for the mode line hint."
:group 'objed-faces)
;; * User Settings and Variables
(defcustom objed-auto-init t
"Whether to enable automatic activation in `objed-mode'.
This option controls whether commands mapped in `objed-cmd-alist'
will activate objed.
This value need to be set before `objed-mode' is activated
otherwise you have to restart `objed-mode' so it can take
effect."
:type 'boolean)
(defcustom objed-auto-init-on-buffer-change nil
"Whether to enable automatic activation on buffer change in `objed-mode'.
This option controls auto activation after the user interactively
switches to other buffers.
This value need to be set before `objed-mode' is activated
otherwise you have to restart `objed-mode' so it can take
effect."
:type 'boolean)
(defcustom objed-disabled-modes '()
"List of modes for which objed should stay disabled.
If the current `major-mode' is in the list or derives from a
member of it `objed' will not activate.
See also `objed-disabled-p'"
:type '(repeat symbol))
(defcustom objed-init-p-function #'objed-init-p
"Function which tests if objed is allowed to initialize.
The function should return nil if objed should not initialize."
:type 'function)
(defcustom objed-init-hook '(objed-init-mode-line objed-init-which-key)
"Hook that runs after objed initialized."
:type 'hook)
(defcustom objed-exit-hook '()
"Hook that runs when objed exits."
:type 'hook)
(defcustom objed-cmd-alist
'((left-char . char)
(right-char . char)
(forward-word . word)
(capitalize-word . word)
(backward-word . word)
(move-beginning-of-line . line)
(move-end-of-line . line)
(previous-line . line)
(next-line . line)
(beginning-of-buffer . buffer)
(end-of-buffer . buffer)
(scroll-up-command . char)
(scroll-down-command . char)
(View-scroll-half-page-forward . char)
(View-scroll-half-page-backward . char)
(move-to-window-line-top-bottom . line)
(imenu . line)
(backward-paragraph . paragraph)
(forward-paragraph . paragraph)
(fill-paragraph . textblock)
(down-list . sexp)
(backward-up-list . sexp)
(up-list . sexp)
(forward-sexp . sexp)
(backward-sexp . sexp)
(indent-pp-sexp . bracket)
(back-to-indentation . line)
(org-beginning-of-line . line)
(org-end-of-line . line)
(recenter-top-bottom . line)
(forward-sentence . sentence)
(org-forward-sentence . sentence)
(backward-sentence . sentence)
(org-backward-sentence . sentence)
(org-backward-element . block)
(beginning-of-defun . defun)
(end-of-defun . defun)
(outline-previous-visible-heading . section)
(outline-next-visible-heading . section)
(org-previous-visible-heading . section)
(comint-previous-prompt . output)
(comint-next-prompt . output)
(forward-button . face)
(backward-button . face)
(sgml-skip-tag-backward . tag)
(Info-next-reference . face)
(Info-prev-reference . face)
(objed-next-identifier . identifier)
(objed-prev-identifier . identifier)
(objed-first-identifier . identifier)
(objed-last-identifier . identifier)
;; editing entry commands
(yank . region)
(yank-pop . region)
;; misc
(kill-buffer . char)
(kill-this-buffer . char)
)
"Entry commands and associated objects."
:type '(alist :key-type sexp
:value-type (choice sexp
(repeat sexp))))
(defcustom objed-switch-alist '()
"Alist mapping objects to region functions.
When switching to an object interactively using its object
command, any mapped function in this alist gets called. The
function receives the beginning and end position of object as
arguments.
This can be used to execute any additional actions when switching
to an object like for example indenting the object. The mapping
for t acts as the default to use when no other mapping for
object exists."
:type '(alist :key-type sexp
:value-type function))
(defcustom objed-switch-functions '(objed-switch-goto-beg)
"Hook that runs after switching to an object.
Functions in this hook get the object name, start and end
position as arguments. This hook runs after any mappings in
`objed-switch-alist'."
:type 'hook)
(defcustom objed-states-max 20
"Maximal number of states to remember.
This option holds the number of times `objed-last' can
be used to restore previous states."
:type 'integer)
(defvar objed--isearch-cmds
'(isearch-forward
isearch-forward-regexp
isearch-forward-symbol
isearch-backward
isearch-backward-regexp
isearch-forward-symbol-at-point
isearch-forward-word)
"Isearch commands known to objed.")
(defcustom objed-keeper-commands
`(save-buffer
read-only-mode
undo
undo-only
delete-other-windows
reposition-window
eval-defun
eval-last-sexp
kmacro-start-macrop
kmacro-start-macro-or-insert-counter
kmacro-end-or-call-macro
kmacro-call-macro
,@objed--isearch-cmds
)
"Regular Emacs commands which should not exit modal edit state.
When regular commands are executed `objed' will exit its editing
state. Commands added to this list wont do that."
:type '(repeat function))
(defcustom objed-cursor-color "#e52b50"
"Cursor color to use when `objed' is active."
:type 'color)
(defcustom objed-which-key-order #'which-key-description-order
"Key sort order to use for which key help popups."
:type 'function)
(define-obsolete-variable-alias 'objed-modeline-hint-p
'objed-modeline-hint "ea0be40dd"
"Whether to show hint for current object in mode line.")
(defcustom objed-modeline-hint t
"Whether to show hint for current object in mode line."
:type 'boolean)
(defcustom objed-mode-line-format
'(:eval (propertize
(format " %s(%s) "
(symbol-name objed--object)
(char-to-string (aref (symbol-name objed--obj-state) 0)))
'face 'objed-mode-line))
"Format used to display hint in mode-line.
Only relevant when `objed-modeline-hint' is non-nil."
:type 'sexp)
(defcustom objed-modeline-setup-func #'objed--setup-mode-line
"Function to setup the mode line.
This function recieves `objed-mode-line-format' as an argument to
add/remove the mode line hint.
It also recieves a second optional argument which indicates if
the hint should be remove or added. If non-nil the hint should be
removed."
:type 'symbol)
(defcustom objed-initial-object 'region
"Object to use as fallback for `objed-activate'."
:type 'symbol)
;; optional dep options
(define-obsolete-variable-alias 'objed-use-which-key-if-available-p
'objed-use-which-key-if-available "ea0be40dd"
"Whether to allow loading and use of `which-key'.
To avoid loading `which-key' set this var before activating `objed-mode.'")
(defcustom objed-use-which-key-if-available t
"Whether to allow loading and use of `which-key'.
To avoid loading `which-key' set this var before activating `objed-mode.'"
:type 'boolean)
(define-obsolete-variable-alias 'objed-auto-wk-top-level-p
'objed-auto-wk-top-level "ea0be40dd"
"Whether to show top level help automatically when activating.
Respects `which-key-idle-delay'.
The top level help is also available via `objed-show-top-level'.")
(defcustom objed-auto-wk-top-level nil
"Whether to show top level help automatically when activating.
Respects `which-key-idle-delay'.
The top level help is also available via `objed-show-top-level'."
:type 'boolean)
(define-obsolete-variable-alias 'objed-use-avy-if-available-p
'objed-use-avy-if-available "ea0be40dd"
"Whether to allow loading and use of `avy'.
To avoid loading `avy' set this var before activating `objed-mode.'")
(defcustom objed-use-avy-if-available t
"Whether to allow loading and use of `avy'.
To avoid loading `avy' set this var before activating `objed-mode.'"
:type 'boolean)
(define-obsolete-variable-alias 'objed-use-hl-p
'objed-use-hl "ea0be40dd"
"Whether allow to use `hl-line' to highlight the current object.")
(defcustom objed-use-hl t
"Whether allow to use `hl-line' to highlight the current object."
:type 'boolean)
;; dyns
(defvar which-key-idle-delay)
(defvar which-key--using-top-level)
(defvar which-key-replacement-alist)
(declare-function objed--exit-objed "objed" nil t)
(declare-function electric-pair-post-self-insert-function "ext:electric")
(declare-function which-key-description-order "ext:which-key")
(declare-function which-key--create-buffer-and-show "ext:which-key")
(declare-function which-key--hide-popup "ext:which-key")
(declare-function avy-process "ext:avy")
(declare-function avy--style-fn "ext:avy")
(declare-function avy-goto-char "ext:avy")
(declare-function edit-indirect-region "ext:edit-indirect")
(declare-function edit-indirect-commit "ext:edit-indirect")
(declare-function electric-pair-syntax-info "ext:elec-pair")
(declare-function hl-line-unhighlight "ext:hl-line")
(declare-function hl-line-highlight "ext:hl-line")
(declare-function multiple-cursors-mode "ext:multiple-cursors")
(declare-function mc/create-fake-cursor-at-point "ext:multiple-cursors")
(declare-function mc/num-cursors "ext:multiple-cursors")
;; * Support for other libs
(with-eval-after-load 'multiple-cursors-core
(when (bound-and-true-p mc/cursor-specific-vars)
(push 'objed--current-obj mc/cursor-specific-vars)
(push 'objed--obj-state mc/cursor-specific-vars)
(push 'objed--object mc/cursor-specific-vars)
(push 'objed--look-around mc/cursor-specific-vars)
(push 'objed--marked-ovs mc/cursor-specific-vars)
(push 'objed--last-states mc/cursor-specific-vars)))
;; * Helper Macros
(defvar objed--exit-alist nil
"Maps operations to exit functions.
If this list contains an entry for an operation, the
exit function is called after execution of the operation.")
(defvar objed--after-init-alist
'((move-beginning-of-line . objed--object-until-bol)
(org-beginning-of-line . objed--object-until-bol)
(move-end-of-line . objed--object-trailing-line)
(org-end-of-line . objed--object-trailing-line)
(back-to-indentation . objed--until-start)
(beginning-of-buffer . objed--until-start)
(end-of-buffer . objed--until-end)
(backward-sentence . objed--goto-start))
"Maps commands which need special initialization to init functions.
The init functions are called with the position point was before
the command was executed.")
(defvar objed--opoint nil
"Position before movement command activated `objed'.")
(defmacro objed-define-op (key cmd &optional exit)
"Macro to create operations for `objed'.
KEY is a string to be interpreted as spelled-out keystrokes,
using same format as for `kbd'. If KEY is non-nil it is bound to
and operation that will use CMD in `objed-map'.
CMD should be a region command or a non-interactive function.
In the latter case the function recieves two buffer positions as
arguments. If the function is able to recieve three arguments the
third argument will be the prefix argument passed to the
operation.
If EXIT is given it should be a symbol or function which is
called after executing the operation. The function recieves one
argument which is the string of the textual content the operation
acted on. See `objed-exit-op'.
This macro returns a command that can be used as an `objed'
operation."
(let ((name (intern (format "objed-%s" (symbol-name cmd))))
(res (list 'progn)))
(when key
(push `(define-key objed-map (kbd ,key) ',name) res))
(when exit
(push `(setq objed--exit-alist
(cons (cons ',name ',exit) objed--exit-alist))
res))
(push `(defun ,name (arg)
"Objed operation."
(interactive "P")
(let ((cmd (objed--create-op ',cmd arg)))
(objed--do cmd ',name)))
res)
(nreverse res)))
(defvar objed--with-allow-input nil)
(defmacro objed--with-allow-input (&rest body)
"Allow input in minibuffer while `objed' is active.
The code executed in BODY allows minibuffer input without
interferring with `objed'."
`(let ((overriding-terminal-local-map nil)
(minibuffer-setup-hook (remq 'objed--reset minibuffer-setup-hook))
(objed--with-allow-input t))
(set-cursor-color objed--saved-cursor)
(unwind-protect (progn ,@body)
;; body might exit objed...
(when objed--buffer
(set-cursor-color objed-cursor-color)))))
(defvar objed--dispatch-key-alist nil
"Store keys for dispatch commands.")
(defmacro objed-define-dispatch (key def)
"Define a dispatch binding.
Defines a prefix key KEY. After pressing this prefix key the user
is expected to invoke an object command bound in
variable/`objed-object-map' to choose an object.
KEY is a string to be interpreted as spelled-out keystrokes,
using same format as for `kbd'.
DEF is the symbol for the function to be called with the choosen
object as an argument."
(declare (indent 2))
(let ((dp (intern (format "objed-%s-dispatch" (symbol-name def)))))
`(prog1 ',def
(push (cons ',dp ',def) objed--dispatch-alist)
(setf (symbol-function (define-prefix-command ',dp))
objed-dispatch-map)
(define-key objed-map (kbd ,key) ',dp)
;; save for possible reinit of objed-map
(push (cons ,key ',dp) objed--dispatch-key-alist))))
(defvar objed-mode nil)
(defvar objed--buffer nil
"Buffer where objed got initialized.")
(defvar objed--dispatch-alist nil
"Maps prefix commands to functions.
Don't modify this list manually, use `objed-define-dispatch'.")
(defun objed-switch-goto-beg (_obj beg _end)
"Move to BEG.
This function move to the beginning of any selected
object via `objed-switch-functions'."
(goto-char beg))
(defun objed--object-dispatch (name)
"Dispatch according to object NAME.
Uses `objed--dispatch-alist' and defaults to
update to given object."
(let* ((cmd (key-binding
(vector
(if (>= (length (this-command-keys-vector)) 2)
(aref (this-command-keys-vector) 0)
;; for testing purposes...
last-command-event))))
(binding (assq cmd objed--dispatch-alist)))
(cond (binding
(funcall (cdr binding) name))
(t
(if objed--buffer
(objed--switch-to name)
(objed--init name))
(when objed--object
(let ((switcher (or (assq name objed-switch-alist)
(assq t objed-switch-alist))))
(when switcher
(funcall (cdr switcher) (objed--beg) (objed--end))))
(run-hook-with-args 'objed-switch-functions name
(objed--beg) (objed--end)))))))
(defun objed--switch-to-object-for-cmd (cmd)
"Guess which object to use.
CMD is the command for which object should be guessed. Returns
cons of guessed object and its state."
(let ((objed--block-p t)
(o (cdr (assq cmd objed-cmd-alist)))
(initf (cdr (assq cmd objed--after-init-alist))))
(if o
(objed--switch-to o (if (eq cmd #'back-to-indentation)
'inner 'whole))
(objed--update-current-object))
(when initf (funcall initf objed--opoint))))
;; * Keymaps
(defmacro objed--call-and-switch (cmd obj &optional before after)
"Create objed command which will invoke CMD and switch to OBJ.
BEFORE and AFTER are forms to execute before/after calling the command."
`(defun ,(intern (format "objed-%s" (symbol-name cmd))) ()
,(format "Call `%s' and switch to object %s"
(symbol-name cmd) (symbol-name obj))
(interactive)
,before
(let* ((prb (and (region-active-p) (= (point) (region-beginning))))
(pre (and (region-active-p) (= (point) (region-end))))
(pos (point)))
(setq this-command ',cmd)
(call-interactively ',cmd)
,after
(objed--switch-to ',obj
(if (eq objed--object ',obj)
objed--obj-state
'whole))
(when (or prb pre)
(cond ((and prb
(= (point) (region-end)))
(set-mark pos))
((and pre
(= (point) (region-beginning)))
(set-mark pos)))))))
(defun objed-quit-window-or-reformat ()
"Quit window for objed."
(interactive)
(let ((nc (let ((overriding-terminal-local-map nil))
(key-binding "q" nil t))))
(if (and (string-match "insert" (symbol-name nc))
(not buffer-read-only))
(progn
(when (eq last-command this-command)
(or (objed--goto-next)
(objed--switch-to 'defun)))
(cond ((and (not (eq objed--object 'defun))
(or (eq major-mode 'fundamental-mode)
(derived-mode-p 'text-mode)
(objed--at-comment-p)
(objed--in-string-or-comment-p)))
(call-interactively 'fill-paragraph)
(objed--switch-to 'textblock)
(message "Filled paragraph."))
((objed--switch-to 'defun)
(indent-region (objed--beg) (objed--end))
(objed--update-current-object)
(message "Indented defun."))))
(objed--reset)
(call-interactively nc))))
(defun objed--point-in-periphery ()
"Return non-nil if point is in current lines periphery."
(<= (point) (save-excursion (back-to-indentation) (point))))
(defvar objed-map
(let ((map (make-sparse-keymap)))
;; block unused chars by default
(dolist (char (number-sequence ?a ?z))
(define-key map (format "%c" char) 'objed-undefined))
;; keep map active for numeric args
(dolist (n (number-sequence ?0 ?9))
(define-key map (format "%c" n) 'digit-argument)
(define-key map (kbd (format "M-%c" n)) 'digit-argument)
(define-key map (kbd (format "C-%c" n)) 'digit-argument))
;; common emacs keys
(define-key map (kbd "C-g") 'objed-quit)
(define-key map (kbd "?") 'objed-show-top-level)
;; (define-key map (kbd "C-o") 'objed-open-line)
;; TODO: switch with q, so quit window is qq?
(define-key map "g" 'objed-quit)
(define-key map "q" 'objed-quit-window-or-reformat)
;; TODO: support repeated invokation
(define-key map (kbd "C-u") 'universal-argument)
;; for quick access
(define-key map "0" 'universal-argument)
(define-key map (kbd "C-SPC") 'set-mark-command)
(define-key map (kbd "C-x C-x") 'objed-exchange-point-and-mark)
;; TODO: birdview mode/scroll mode
(define-key map (kbd "C-h k") 'objed-describe-key)
(define-key map (kbd "C-h n") 'which-key-show-next-page-cycle)
(define-key map (kbd "C-h p") 'which-key-show-previous-page-cycle)
(define-key map (kbd "C-M-w") 'objed-append-mode)
;; use uppercase as C-M replacement
(define-key map (kbd "W") 'objed-append-mode)
;; todo: restore object state, too?
(define-key map "/" (objed--call-and-switch undo char))
(define-key map "~" 'objed-undo-in-object)
;; general movement
(define-key map "l" (objed--call-and-switch right-char char))
(define-key map "h" (objed--call-and-switch left-char char))
(define-key map "L" 'objed-move-char-forward)
(define-key map "H" 'objed-move-char-backward)
(define-key map "s" (defun objed-forward-word ()
"Call `forward-word' and switch to object word"
(interactive)
(if (objed--inner-p)
(let* ((subword-mode t)
(superword-mode nil)
(find-word-boundary-function-table
subword-find-word-boundary-function-table))
(setq this-command 'forward-word)
(call-interactively 'forward-word))
(setq this-command 'forward-word)
(call-interactively 'forward-word))
(objed--switch-to 'word
(if (eq objed--object 'word)
objed--obj-state
'whole))))
(define-key map "r" (defun objed-backward-word ()
"Call `backward-word' and switch to object word"
(interactive)
(if (objed--inner-p)
(let* ((subword-mode t)
(superword-mode nil)
(find-word-boundary-function-table
subword-find-word-boundary-function-table))
(setq this-command 'backward-word)
(call-interactively 'backward-word))
(setq this-command 'backward-word)
(call-interactively 'backward-word))
(objed--switch-to 'word
(if (eq objed--object 'word)
objed--obj-state
'whole))))
(define-key map "S" 'objed-move-word-forward)
(define-key map "R" 'objed-move-word-backward)
(define-key map "f" (objed--call-and-switch
objed--forward-sexp sexp))
(define-key map "b" (objed--call-and-switch
objed--backward-sexp sexp))
(define-key map "F" 'objed-move-object-forward)
(define-key map "B" 'objed-move-object-backward)
(define-key map "p" (objed--call-and-switch
previous-line line
nil
(when (objed--point-in-periphery)
(back-to-indentation))))
(define-key map "n" (objed--call-and-switch
next-line line
nil
(when (objed--point-in-periphery)
(back-to-indentation))))
(define-key map "N" 'objed-move-line-forward)
(define-key map "P" 'objed-move-line-backward)
(define-key map "(" 'objed-backward-until-context)
(define-key map ")" 'objed-forward-until-context)
(define-key map "[" 'objed-previous) ;; objed-current-or-previous-context
(define-key map "]" 'objed-next) ;; objed-current-or-next-context
(define-key map "{" (objed--call-and-switch backward-paragraph paragraph))
(define-key map "}" (defun objed-forward-paragraph ()
(interactive)
(call-interactively 'forward-paragraph)
(unless (eq last-command this-command)
(objed--skip-ws t))
(objed--switch-to 'paragraph)))
(define-key map (kbd "<home>") 'objed-top-object)
(define-key map (kbd "<end>") 'objed-bottom-object)
(define-key map "<" 'objed-top-object)
(define-key map ">" 'objed-bottom-object)
;; block expansions
(define-key map "a" 'objed-beg-of-block)
(define-key map "e" 'objed-end-of-block)
(define-key map "v" 'objed-expand-block)
;; context expansions
(define-key map "o" 'objed-expand-context)
(define-key map "O" 'objed-current-or-previous-context)
(define-key map "i" 'objed-del-insert)
(define-key map "t" 'objed-toggle-state)
(define-key map "j" 'objed-toggle-side)
;; marking/unmarking
(define-key map "m" 'objed-mark)
;; mark upwards
(define-key map "M" 'objed-toggle-mark-backward)
;; (define-key map "M" 'objed-unmark-all)
(define-key map "@" 'objed-extend)
;; TODO: second + include more
(define-key map "+" 'objed-include-forward)
;; (define-key map "" 'objed-include-backward)
;; basic edit ops
(define-key map "k" 'objed-kill)
(define-key map "K" 'objed-kill)
(define-key map "w" 'objed-copy)
(define-key map "d" 'objed-delete)
(define-key map "D" 'objed-delete)
(define-key map "y" 'objed-yank)
(define-key map (kbd "\\")
;; dont exit
(objed-define-op nil objed-indent ignore))
(define-key map ";"
(objed-define-op nil objed-comment-or-uncomment-region))
(define-key map "$"
(objed-define-op nil flyspell-region))
;; quote op
(define-key map "\""
(objed-define-op nil objed-electric-pair))
;; (define-key map "\""
;; (objed-define-op nil objed-electric))
;; direct object switches
(define-key map "." 'objed-goto-next-identifier)
(define-key map "," 'objed-goto-prev-identifier)
(define-key map "_" 'objed-toggle-indentifier-place)
;; prefix keys
(define-key map "x" 'objed-op-map)
(define-key map "c" 'objed-object-map)
;; for custom user object and op commands
(define-key map "'" 'objed-user-map)
(define-key map "-" 'objed-other-user-map)
(define-key map (kbd "M-g o") 'objed-occur)
;; special commands
(define-key map "*" 'objed-mark-more)
(define-key map "u" 'objed-last)
;; zap to object, jump to objects with avy
(define-key map "z" 'objed-ace)
;; swiper like object search
;; TODO: start query replace in current object,
;; or for all
(define-key map "%" 'objed-replace)
(define-key map ":" 'objed-eval-expression)
(define-key map "&"
(objed-define-op nil objed-pipe-region))
(define-key map "|"
(objed-define-op nil objed-ipipe))
(define-key map "!" 'objed-execute)
(define-key map (kbd "<C-return>")
(objed-define-op
nil objed-run-or-eval ignore))
(define-key map (kbd "<S-return>")
(objed-define-op nil objed-comment-duplicate))
(define-key map (kbd "<M-return>")
(objed-define-op nil objed-duplicate-down))
(define-key map (kbd "<C-M-return>")
'objed-insert-new-object)
(define-key map "^" 'objed-raise)
;; move windows
(define-key map (kbd "<s-left>") 'objed-move-window-line-left)
(define-key map (kbd "<s-right>") 'objed-move-window-line-right)
(define-key map (kbd "<s-up>") 'objed-move-window-line-up)
(define-key map (kbd "<s-down>") 'objed-move-window-line-down)
;; move text
(define-key map (kbd "<C-left>") 'objed-indent-left)
(define-key map (kbd "<C-right>") 'objed-indent-right)
(define-key map (kbd "<M-right>") 'objed-indent-to-right-tab-stop)
(define-key map (kbd "<M-left>") 'objed-indent-to-left-tab-stop)
(define-key map (kbd "<C-M-left>") 'objed-forward-barf-sexp)
(define-key map (kbd "<C-M-right>") 'objed-forward-slurp-sexp)
(define-key map (kbd "<C-S-left>") 'objed-forward-barf-sexp)
(define-key map (kbd "<C-S-right>") 'objed-forward-slurp-sexp)
(define-key map (kbd " <S-left>") 'objed-move-object-backward)
(define-key map (kbd " <S-right>") 'objed-move-object-forward)
;; for some objects up down is more intuitive
(define-key map (kbd " <S-up>") 'objed-move-object-backward)
(define-key map (kbd " <S-down>") 'objed-move-object-forward)
map)
"Keymap for commands when `objed' is active.")
(defun objed--define-prefix (key cmd)
"Create a prefix keymap for `objed-map'.
The keymap will be accessible with KEY and bound to prefix command
CMD.
The keymap is initilized with basic bindings for numeric
arguments and help.
Other single character keys are bound to `objed-undefined'."
(let ((map (define-prefix-command cmd)))
;; init as prefix
(define-key objed-map (kbd key) map)
;; basic bindings
(dolist (char (number-sequence ?a ?z))
(define-key map (kbd (format "%c" char)) 'objed-undefined))
(let (loop)
(define-key map "-" 'negative-argument)
;; Make plain numbers do numeric args.
(setq loop ?0)
(while (<= loop ?9)
(define-key map (char-to-string loop) 'digit-argument)
(setq loop (1+ loop))))
(define-key map (kbd "C-h") 'objed-describe-prefix-bindings)
map))
(autoload 'dired-jump "dired-x" nil t)
(defvar objed-op-map
(let ((map (objed--define-prefix "x" 'objed-op-map)))
;; apply region command on object
(define-key map (kbd "TAB") 'objed-op-x)
;; todo: show object op hydra command
(define-key map "c"
;; upcase, downcase, capitalize, reformat
(objed-define-op nil objed-case-op))
(define-key map "x" 'objed-eval-defun)
(define-key map "e" 'objed-eval-exp)
(define-key map "y" 'objed-insert)
(define-key map "i" 'insert-file)
(define-key map "q" 'read-only-mode)
(define-key map "r" ctl-x-r-map)
(define-key map "n" 'objed-narrow)
;; TODO: undo propose integration
(define-key map "u" (objed--call-and-switch undo char))
(define-key map "d" 'dired-jump)
;; (define-key map "z" 'objed-repeat)
;; actions analog to C-x C-KEY which exit
(define-key map "s" 'save-buffer)
(define-key map "f" 'find-file)
(define-key map "w" 'write-file)
(define-key map "v" 'find-alternate-file)
(define-key map "b" 'switch-to-buffer)
(define-key map "o" 'objed-other-window)
(define-key map "k" 'objed-kill-buffer)
(define-key map "j" 'imenu)
(define-key map "0" 'delete-window)
(define-key map "1" 'delete-other-windows)
(define-key map "2" 'split-window-vertically)
(define-key map "3" 'split-window-horizontally)
map)
"Map for additional operations called via a prefix from `objed-map'.
To define new operations see `objed-define-op'.")
(defun objed-repeat ()
"Repeat last command for objed."