-
Notifications
You must be signed in to change notification settings - Fork 0
/
p4.el
5156 lines (4618 loc) · 208 KB
/
p4.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
;;; p4.el --- Perforce-Emacs Integration -*- lexical-binding: t; -*-
;; Copyright (c) 1996-1997 Eric Promislow
;; Copyright (c) 1997-2004 Rajesh Vaidheeswarran
;; Copyright (c) 2005 Peter Osterlund
;; Copyright (c) 2009 Fujii Hironori
;; Copyright (c) 2012 Jason Filsinger
;; Copyright (c) 2013-2015 Gareth Rees <gdr@garethrees.org>
;; Copyright (c) 2015-2024 John Ciolfi
;; Version: 14.0
;; This version started with the 2015 Version 12.0 from Gareth Rees <gdr@garethrees.org>
;; https://github.com/gareth-rees/p4.el
;;
;; This version has significant changes, features, fixes, and performance improvements. One
;; example difference is the elimination of the Perforce status in the mode line. Perforce
;; interactions can be slow and this slowed Emacs. Now all interactions with Perforce are explicit
;; and invoked from a P4 menu selection or keybinding. This means that Emacs will be performant
;; even if the Perforce server is slow or not responding. By default, most commands prompt you to
;; run the action requests, which lets you provide additional switches if desired.
;;; Commentary:
;; p4.el integrates the Perforce software version management system
;; into Emacs. It is designed for users who are familiar with Perforce
;; and want to access it from Emacs: it provides Emacs interfaces that
;; map directly to Perforce commands.
;;; License:
;; 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 2 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, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;; Installation:
;; 1. Download p4.el and compile it:
;;
;; emacs -Q -batch -f batch-byte-compile /path/to/dir/containing/p4.el
;;
;; 2. In your .emacs add:
;;
;; (add-to-list 'load-path "/path/to/dir/containing/p4")
;; (require 'p4)
;;
;; By default, the P4 global key bindings start with C-c p. If you prefer a different key prefix,
;; then you should customize the setting p4-global-key-prefix.
;;; Code:
(require 'calendar)
(require 'comint)
(require 'dired)
(require 'diff-mode)
(require 'font-lock)
(eval-when-compile (require 'cl-lib))
(defvar p4-version "14" "Perforce-Emacs Integration version.")
;; Forward declarations to avoid byte-compile warning "reference to free variable"
(defvar p4-global-key-prefix)
(defvar p4-basic-mode-map)
(defvar p4-annotate-mode-map)
;;; User options:
(defgroup p4 nil "Perforce VC System." :group 'tools)
(eval-and-compile
;; This is needed at compile time by p4-help-text.
(defcustom p4-executable
(locate-file "p4" (append exec-path '("/usr/local/bin" "~/bin" ""))
(if (memq system-type '(ms-dos windows-nt)) '(".exe"))
#'file-executable-p)
"The p4 executable."
:type 'string
:group 'p4))
(defcustom p4-default-describe-options "-s"
"Options to pass to `p4-describe'."
:type 'string
:group 'p4)
(defcustom p4-default-describe-diff-options "-a -du"
"Perforce, p4 describe, options for `p4-describe-with-diff'."
:type 'string
:group 'p4)
(defcustom p4-default-diff-options "-du"
"Options to pass to `p4-diff', `p4-diff2'.
Set to:
-du[n] Unified output format, optional [n] is number of context lines
-dn RCS output format (not recommended in Emacs)
-ds Summary output format (not recommended in Emacs)
-dc[n] Context output format, optional [n] is number of context lines
(not recommended in Emacs)
-dw Ignore whitespace altogether, implies -dl
-db Ignore changes made within whitespace, implies -dl
-dl Ignore line endings"
:type 'string
:group 'p4)
(defcustom p4-default-resolve-options "..."
"Options to pass to `p4-resolve'."
:type 'string
:group 'p4)
(defcustom p4-check-empty-diffs nil
"If non-NIL, check for files with empty diffs before submitting."
:type 'boolean
:group 'p4)
(defcustom p4-follow-symlinks t
"If non-NIL, call `file-truename' on all opened files.
In addition, call `p4-refresh-buffer-with-true-path' before running p4
commands."
:type 'boolean
:group 'p4)
(defcustom p4-synchronous-commands '(add delete edit lock logout reopen revert
unlock)
"List of Perforce commands that are run synchronously."
:type (let ((cmds '(add branch branches change changes client clients delete
describe diff diff2 edit filelog files fix fixes flush
fstat group groups have info integ job jobs jobspec label
labels labelsync lock logout move opened passwd print
reconcile reopen revert set shelve status submit sync
tickets unlock unshelve update user users where)))
(cons 'set (cl-loop for cmd in cmds collect (list 'const cmd))))
:group 'p4)
(defcustom p4-password-source nil
"Action to take when Perforce needs a password.
If NIL, prompt the user to enter password.
Otherwise, this is a string containing a shell command that
prints the password. This command is run in an environment where
P4PORT and P4USER and set from the current Perforce settings."
:type '(radio (const :tag "Prompt user to enter password." nil)
(const :tag "Fetch password from OS X Keychain.\n\n\tFor each Perforce account, use Keychain Access to create an\n\tapplication password with \"Account\" the Perforce user name\n\t(P4USER) and \"Where\" the Perforce server setting (P4PORT).\n"
"security find-generic-password -s $P4PORT -a $P4USER -w")
(const :tag "Fetch password from Python keyring.\n\n\tFor each Perforce account, run:\n\t python -c \"import keyring,sys;keyring.set_password(*sys.argv[1:])\" \\\n\t P4PORT P4USER PASSWORD\n\treplacing P4PORT with the Perforce server setting, P4PORT with the\n\tPerforce user name, and PASSWORD with the password.\n"
"python -c \"import keyring, sys; print(keyring.get_password(*sys.argv[1:3]))\" \"$P4PORT\" \"$P4USER\"")
(string :tag "Run custom command"))
:group 'p4)
(defcustom p4-mode-hook nil
"Hook run by `p4-mode'."
:type 'hook
:group 'p4)
(defcustom p4-form-mode-hook nil
"Hook run by `p4-form-mode'."
:type 'hook
:group 'p4)
(defcustom p4-file-form-mode-hook nil
"Hook run by `p4-file-form-mode'."
:type 'hook
:group 'p4)
(defcustom p4-edit-hook nil
"Hook run after opening a file for edit."
:type 'hook
:group 'p4)
(defcustom p4-set-client-hooks nil
"Hook run after client is changed."
:type 'hook
:group 'p4)
(defcustom p4-strict-complete t
"If non-NIL, `p4-set-my-client' requires an exact match."
:type 'boolean
:group 'p4)
(defcustom p4-cleanup-time 600
"Perforce cache timeout.
Time in seconds after which a cache of information from the
Perforce server becomes stale."
:type 'integer
:group 'p4)
(defcustom p4-my-clients nil
"Clients tracked for the current Emacs session.
The list of Perforce clients that the function
`p4-set-client-name' will complete on, or NIL if it should
complete on all clients."
:type '(repeat (string))
:group 'p4)
(eval-and-compile
;; This is needed at compile time by p4-help-text.
(defcustom p4-modify-args-function #'identity
"Function that modifies a Perforce command line argument list.
All calls to the Perforce executable are routed through this
function to enable global modifications of argument vectors. The
function will be called with one argument, the list of command
line arguments for Perforce (excluding the program name). It
should return a possibly modified command line argument list.
This can be used to e.g. support wrapper scripts taking custom
flags."
:type 'function
:group 'p4))
(defcustom p4-branch-from-depot-filespec-function nil
"Function that extracts a branch from a depot file spec.
This takes one argument a depot path, e.g. //branch/name/path/to/file.ext
and should return the //branch/name port if possible or nil."
:type 'function
:group 'p4)
(defgroup p4-faces nil "Perforce VC System Faces." :group 'p4)
(defface p4-description-face '((t (:inherit font-lock-doc-face)))
"Face used for change descriptions."
:group 'p4-faces)
(defface p4-heading-face '((t))
"Face used for section heading."
:group 'p4-faces)
(defface p4-link-face '((t (:weight bold)))
"Face used to highlight clickable links."
:group 'p4-faces)
(defface p4-action-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce actions (add/edit/integrate/delete)."
:group 'p4-faces)
(defface p4-branch-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce branches."
:group 'p4-faces)
(defface p4-change-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce change numbers."
:group 'p4-faces)
(defface p4-client-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce users."
:group 'p4-faces)
(defface p4-filespec-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce filespec."
:group 'p4-faces)
(defface p4-job-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce job names."
:group 'p4-faces)
(defface p4-label-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce labels."
:group 'p4-faces)
(defface p4-revision-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce revision numbers."
:group 'p4-faces)
(defface p4-user-face '((t (:inherit p4-link-face)))
"Face used to highlight Perforce users."
:group 'p4-faces)
(defface p4-depot-add-face
'((((class color) (background light)) (:foreground "blue"))
(((class color) (background dark)) (:foreground "cyan")))
"Face used for files open for add."
:group 'p4-faces)
(defface p4-depot-branch-face
'((((class color) (background light)) (:foreground "blue4"))
(((class color) (background dark)) (:foreground "sky blue")))
"Face used for files open for integrate."
:group 'p4-faces)
(defface p4-depot-delete-face
'((((class color) (background light)) (:foreground "red"))
(((class color) (background dark)) (:foreground "pink")))
"Face used for files open for delete."
:group 'p4-faces)
(defface p4-depot-edit-face
'((((class color) (background light)) (:foreground "dark green"))
(((class color) (background dark)) (:foreground "light green")))
"Face used for files open for edit."
:group 'p4-faces)
(defface p4-depot-move-delete-face
'((((class color) (background light)) (:foreground "brown"))
(((class color) (background dark)) (:foreground "brown")))
"Face used for files open for delete."
:group 'p4-faces)
(defface p4-depot-move-add-face
'((((class color) (background light)) (:foreground "navy"))
(((class color) (background dark)) (:foreground "CadetBlue1")))
"Face used for files open for add."
:group 'p4-faces)
(defface p4-form-comment-face '((t (:inherit font-lock-comment-face)))
"Face for comment in P4 Form mode."
:group 'p4-faces)
(defface p4-form-keyword-face '((t (:inherit font-lock-keyword-face)))
"Face for keyword in P4 Form mode."
:group 'p4-faces)
(defface p4-highlight-face
'((((class color) (background light))
(:foreground "Firebrick" :background "yellow"))
(((class color) (background dark))
(:foreground "chocolate1" :background "blue3")))
"Face used for highlight items."
:group 'p4-faces)
(defcustom p4-annotate-line-number-threshold 50000
"In p4-annotate, show source line numbers when below this threshold.
Line number background is shaded based on Adige."
:type 'integer
:group 'p4)
;; White to black background shades for p4-annotate-line-ageNNN-face:
;; https://www.w3schools.com/colors/colors_picker.asp?colorhex=000000
(defface p4-annotate-line-age1-face
'((t :foreground "black"
:background "#f2f2f2"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age2-face
'((t :foreground "black"
:background "#e6e6e6"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age3-face
'((t :foreground "black"
:background "#d9d9d9"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age4-face
'((t :foreground "black"
:background "#cccccc"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age5-face
'((t :foreground "black"
:background "#bfbfbf"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age6-face
'((t :foreground "black"
:background "#b3b3b3"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age7-face
'((t :foreground "black"
:background "#a6a6a6"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age8-face
'((t :foreground "white"
:background "#999999"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age9-face
'((t :foreground "white"
:background "#8c8c8c"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age10-face
'((t :foreground "white"
:background "#808080"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age11-face
'((t :foreground "white"
:background "#737373"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age12-face
'((t :foreground "white"
:background "#666666"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age13-face
'((t :foreground "white"
:background "#595959"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age14-face
'((t :foreground "white"
:background "#4d4d4d"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age15-face
'((t :foreground "white"
:background "#404040"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age16-face
'((t :foreground "white"
:background "#333333"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age17-face
'((t :foreground "white"
:background "#262626"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age18-face
'((t :foreground "white"
:background "#1a1a1a"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age19-face
'((t :foreground "white"
:background "#0d0d0d"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defface p4-annotate-line-age20-face
'((t :foreground "white"
:background "#000000"))
"Face used in p4-annotate for line age."
:group 'p4-faces)
(defvar p4-annotate-line-first-360-day-faces
'(p4-annotate-line-age1-face
p4-annotate-line-age2-face
p4-annotate-line-age3-face
p4-annotate-line-age4-face
p4-annotate-line-age5-face
p4-annotate-line-age6-face)
"List of faces for showing annotation line age in first 360 days.")
(defvar p4-annotate-line-year-faces
'(p4-annotate-line-age7-face
p4-annotate-line-age8-face
p4-annotate-line-age9-face
p4-annotate-line-age10-face
p4-annotate-line-age11-face
p4-annotate-line-age12-face
p4-annotate-line-age13-face
p4-annotate-line-age14-face
p4-annotate-line-age15-face
p4-annotate-line-age16-face
p4-annotate-line-age17-face
p4-annotate-line-age18-face
p4-annotate-line-age19-face
p4-annotate-line-age20-face)
"List of faces for showing annotation line age after 360 days.")
;; Local variables in all buffers.
(defvar-local p4-mode nil "P4 minor mode.")
;; Local variables in P4 process buffers.
(defvar-local p4-process-args nil "List of p4 command and arguments.")
(defvar-local p4-process-callback nil
"Function run when p4 command completes successfully.")
(defvar-local p4-process-after-show nil
"Function run after showing output of successful p4 command.")
(defvar-local p4-process-auto-login nil
"If non-NIL, automatically prompt user to log in.")
(defvar-local p4-process-buffers nil
"List of buffers whose status is being updated here.")
(defvar-local p4-process-pending nil
"Pending status update structure being updated here.")
(defvar-local p4-process-pop-up-output nil
"Pop-up window?
Function that returns non-NIL to display output in a pop-up
window, or NIL to display it in the echo area.")
(defvar-local p4-process-synchronous nil
"If non-NIL, run p4 command synchronously.")
;; Local variables in P4 Form buffers.
(defvar-local p4-form-commit-command nil
"Perforce, p4 command to run when committing this form.")
(defvar-local p4-form-commit-success-callback nil
"Callback for p4 commit.
Function run if commit succeeds. It receives two arguments:
the commit command and the buffer containing the output from the
commit command.")
(defvar-local p4-form-commit-failure-callback nil
"Callback for p4 commit failures.
Function run if commit fails. It receives two arguments:
the commit command and the buffer containing the output from the
commit command.")
(defvar-local p4-form-head-text
(format "# Created using Perforce-Emacs Integration version %s.
# Type C-c C-c to send the form to the server.
# Type C-x k to cancel the operation.
#\n" p4-version)
"Text added to top of generic form.")
;; Local variables in P4 depot buffers.
(defvar-local p4-default-directory nil "Original value of `default-directory'.")
(defvar-local p4--opened-args nil "Used internally by `p4-opened'.")
;;; P4 minor mode:
(add-to-list 'minor-mode-alist '(p4-mode p4-mode))
;;; Keymap:
(defvar p4-prefix-map
(let ((map (make-sparse-keymap)))
(define-key map "a" 'p4-add)
(define-key map "A" 'p4-fstat)
(define-key map "b" 'p4-branch)
(define-key map "B" 'p4-branches)
(define-key map "c" 'p4-client)
(define-key map "C" 'p4-change)
(define-key map (kbd "M-c") 'p4-changes)
(define-key map "d" 'p4-diff2)
(define-key map "D" 'p4-describe)
(define-key map "\C-d" 'p4-describe-with-diff)
(define-key map (kbd "M-d") 'p4-describe-all-files)
(define-key map "e" 'p4-edit)
(define-key map "E" 'p4-reopen)
(define-key map "\C-f" 'p4-depot-find-file)
(define-key map "f" 'p4-filelog)
(define-key map "F" 'p4-files)
(define-key map "G" 'p4-get-client-name)
(define-key map "g" 'p4-update)
(define-key map "h" 'p4-help)
(define-key map "H" 'p4-have)
(define-key map "i" 'p4-info)
(define-key map "I" 'p4-integ)
(define-key map "j" 'p4-job)
(define-key map "J" 'p4-jobs)
(define-key map "l" 'p4-label)
(define-key map "L" 'p4-labels)
(define-key map "\C-l" 'p4-labelsync)
(define-key map "m" 'p4-move)
(define-key map "o" 'p4-opened)
(define-key map "p" 'p4-print)
(define-key map "P" 'p4-set-p4-port)
(define-key map "\C-p" 'p4-changes-pending)
(define-key map "q" 'quit-window)
(define-key map "r" 'p4-revert-dwim)
(define-key map "R" 'p4-refresh)
(define-key map "\C-r" 'p4-resolve)
(define-key map "s" 'p4-status)
(define-key map "S" 'p4-submit)
(define-key map (kbd "M-s") 'p4-shelve)
(define-key map "\C-s" 'p4-changes-shelved)
(define-key map (kbd "M-u") 'p4-unshelve)
(define-key map "u" 'p4-user)
(define-key map "U" 'p4-users)
(define-key map "v" 'p4-version)
(define-key map "V" 'p4-annotate)
(define-key map "w" 'p4-where)
(define-key map "x" 'p4-delete)
(define-key map "X" 'p4-fix)
(define-key map "z" 'p4-reconcile)
(define-key map "=" 'p4-diff)
(define-key map (kbd "C-=") 'p4-diff-all-opened)
(define-key map (kbd "M-=") 'p4-diff-all-opened-side-by-side)
(define-key map "-" 'p4-ediff)
(define-key map "`" 'p4-ediff-with-head)
(define-key map "_" 'p4-ediff2)
map)
"The prefix map for Perforce, p4.el, commands.")
(fset 'p4-prefix-map p4-prefix-map)
(defun p4-update-global-key-prefix (symbol value)
"Update the P4 global key prefix.
This uses the `p4-global-key-prefix' user setting along with SYMBOL and VALUE."
(set symbol value)
(let ((map (current-global-map)))
;; Remove old binding(s).
(dolist (key (where-is-internal p4-prefix-map map))
(define-key map key nil))
;; Add new binding.
(when p4-global-key-prefix
(define-key map p4-global-key-prefix p4-prefix-map))))
;; From https://www.gnu.org/software/emacs/manual/html_node/elisp/Key-Binding-Conventions.html
;; In summary, the general rules are:
;; C-x reserved for Emacs native essential keybindings:
;; buffer, window, frame, file, directory, etc...
;; C-c reserved for user and major mode:
;; C-c letter reserved for user. <F5>-<F9> reserved for user.
;; C-c C-letter reserved for major mode.
;; Don't rebind C-g, C-h and ESC.
(defcustom p4-global-key-prefix (kbd "C-c p")
"The global key prefix for P4 commands."
:type '(radio (const :tag "No global key prefix" nil) (key-sequence))
:set 'p4-update-global-key-prefix
:group 'p4)
(defcustom p4-prompt-before-running-cmd t
"Before running a p4 command prompt user for the arguments.
This is equivalent to running \\[universal-argument] `universal-argument' before the
p4 command."
:type 'boolean
:group 'p4)
;;; Menu:
(easy-menu-define p4-menu nil "Perforce menu."
`("P4"
["Add" p4-add
:help "M-x p4-add
Open a new file to add it to the depot"]
["Edit" p4-edit
:help "M-x p4-edit
Open an existing file for edit"]
["Revert" p4-revert-dwim
:help "M-x p4-revert-dwim
Discard changes from an opened file(s). If buffer is visiting file p4 revert the file,
otherwise run p4 revert."]
["Delete" p4-delete
:help "M-x p4-delete
Open an existing file for deletion from the depot"]
["Move open file (rename)" p4-move
:help "M-x p4-move
Move file(s) from one location to another"]
["Reopen (move between changelists or change file type)" p4-reopen
:help "M-x p4 reopen
Change the filetype of an open file or move it to another changelist.
Tip: 'p4 reopen -c CN FILE' to move FILE to changelist num, CN"]
["--" nil nil]
["File attributes (p4 fstat)" p4-fstat
:help "M-x p4-fstat
Display file attributes - have revision, etc."]
["File log" p4-filelog]
["--" nil nil]
("Diff"
["EDiff current" p4-ediff
:help "M-x p4-ediff
Ediff file with its original client version"]
["EDiff two versions" p4-ediff2
:help "M-x p4-ediff2
Ediff two versions of a depot file"]
["EDiff current with head" p4-ediff-with-head
:help "M-x p4-ediff-with-head
Ediff file with head version"]
["Diff file with its original client version" p4-diff
:help "M-x p4-diff"]
["Diff file with its original client" p4-diff2
:help "M-x p4-diff2"]
["Diff all opened files" p4-diff-all-opened
:help "M-x p4-diff-all-opened"]
["Diff all opened files side-by-side" p4-diff-all-opened-side-by-side
:help "M-x p4-diff-all-opened-side-by-side"]
)
["--" nil nil]
("Changes"
["Show opened files" p4-opened
:help "M-x p4-opened"]
["Show changes pending" p4-changes-pending
:help "M-x p4-changes-pending
Show pending changelists"]
["Show changes shelved" p4-changes-shelved
:help "M-x p4-changes-shelved
Show shelved changelists"]
["Show changes submitted" p4-changes
:help "M-x p4-changes
Show submitted changelists"]
["--" nil nil]
["Describe change" p4-describe
:help "M-x p4-describe
Run 'p4 describe -s CHANGE_NUM' on changelist"]
["Describe change with diff" p4-describe-with-diff
:help "M-x p4-describe-with-diff
Run 'p4 describe -a -du CHANGE_NUM' on changelist"]
["Describe change showing affected and shelved files" p4-describe-all-files
:help "M-x p4-describe-all-files
Show all affected and shelved files in a changelist."]
["--" nil nil]
["Change" p4-change
:help "M-x p4-change
Create, update, submit, or delete a changelist"]
["Shelve" p4-shelve
:help "M-x p4-shelve
Store files from a pending changelist into the depot"]
["Unshelve" p4-unshelve
:help "M-x p4-unshelve
Restore shelved files from a pending change into a workspace"]
["Submit" p4-submit
:help "M-x p4-submit
Submit opened files to depot"]
["Job" p4-job
:help "M-x p4-job
Create or edit a job (defect) specification"]
["Jobs" p4-jobs
:help "M-x p4-jobs
Display list of jobs"]
["Fix (link job to changelist)" p4-fix
:help "M-x p4-fix
Mark jobs as being fixed by the specified changelist"]
)
("Workspace"
["Update files from depot" p4-update
:help "M-x p4-update
Synchronize the client with its view of the depot"]
["Sync" p4-sync
:help "M-x p4-sync
Synchronize the client with its view of the depot"]
["Sync specific changelist" p4-sync-changelist
:help "M-x p4-sync-changelist
Run 'p4 sync @=CHANGE_NUM' to sync ONLY the contents of a CHANGE_NUM"]
["Refresh (sync -f) file" p4-refresh
:help "M-x p4-refresh
Refresh contents of an unopened file: p4 sync -f FILE"]
["Status of files on client" p4-status
:help "M-x p4-status
Previews output of open files for add, delete, and/or edit in order to reconcile a workspace
with changes made outside of Perforce"]
["Reconcile files with depot" p4-reconcile
:help "M-x p4-reconcile
Open files for add, delete, and/or edit to reconcile
client with workspace changes made outside of Perforce"]
["List files in depot" p4-files
:help "M-x p4-files"]
["Get client name" p4-get-client-name
:help "M-x p4-get-client-name"]
["Unload" p4-unload
:help "M-x p4-unload
Unload a client, label, or task stream to the unload depot"]
["Reload" p4-reload
:help "M-x p4-reload
Reload an unloaded client, label, or task stream"]
["Have (list files in workspace)" p4-have
:help "M-x p4-have
List the revisions most recently synced to workspace"]
["Show where file is mapped" p4-where
:help "M-x p4-where
Show how file names are mapped by the client view"]
["--" nil nil]
["List clients" p4-clients
:help "M-x p4-clients
Display list of clients. Example: p4-clients -u USERNAME -m 100"]
)
["--" nil nil]
["Open for integrate" p4-integ
:help "M-x p4-integ
Integrate one set of files into another"]
["Resolve conflicts" p4-resolve
:help "M-x p4-resolve
Resolve integrations and updates to workspace files"]
["--" nil nil]
["View (print) depot file" p4-print
:help "M-x p4-print
Visit version of file in a buffer"]
["Annotate" p4-annotate
:help "M-x p4-annotate
Use 'p4 annotate -I' to follow integrations into a file along with additional p4 commands
to annotate each line with info from the changelist which introduced the change"]
["Find File using Depot Spec" p4-depot-find-file
:help "M-x p4-depot-find-file
Visit client file corresponding to depot FILESPEC if possible,
otherwise print FILESPEC to a new buffer"]
["--" nil nil]
("Config"
["Branch" p4-branch
:help "M-x p4-branch
Create, modify, or delete a branch view specification"]
["Branches" p4-branches
:help "M-x p4-branches
Display list of branch specifications"]
["Edit a label specification" p4-label
:help "M-x p4-label
Create or edit a label specification"]
["Display list of defined labels" p4-labels
:help "M-x p4-labels"]
["Apply label to workspace" p4-labelsync
:help "M-x p4-labelsync"]
["Edit a client specification" p4-client
:help "M-x p4-client
Create or edit a client workspace specification and its view workspace"]
["Edit a user specification" p4-user
:help "M-x p4-user
Create or edit a user specification"]
["List Perforce users" p4-users
:help "M-x p4-users"]
["--" nil nil]
["Set P4CONFIG" p4-set-p4-config
:help "M-x p4-set-p4-config
Set the P4CONFIG environment variable to VALUE
P4CONFIG is typically set to the filename '.perforce' and this
file is placed at the root of your Perforce workspace. Within
this file, you place Perforce environment variables, such as
P4CLIENT=client_name"]
["Set P4CLIENT" p4-set-client-name
:help "M-x p4-set-client-name
Set the P4CLIENT environment variable to VALUE"]
["Set P4PORT" p4-set-p4-port
:help "M-x p4-set-p4-port
Set the P4PORT environment variable to VALUE."]
["Show client info" p4-set
"M-x p4-set
Set or display Perforce variables."]
["Show server info" p4-info
:help "M-x p4-info
Display client/server information."]
["About P4" p4-version
:help "M-x p4-version
Display the Perforce-Emacs package, p4.el, version"]
)
["Quit WINDOW and bury its buffer" quit-window]
["Help" p4-help
:help "M-x p4-help
Run p4 help CMD"]
))
(defcustom p4-after-menu 'tools
"Top-level menu to place P4 after."
:type 'symbol
:group 'p4)
;; Put P4 menu after the desired menu
(define-key-after (lookup-key global-map [menu-bar]) [p4-menu]
(cons "P4" p4-menu)
p4-after-menu)
;;; Running Perforce (defun's required for macros)
(eval-and-compile
;; This is needed at compile time by p4-help-text.
(defun p4-executable ()
"Check if `p4-executable' is NIL, and if so, prompt the user
for a valid `p4-executable'."
(interactive)
(or p4-executable (call-interactively 'p4-set-p4-executable))))
(eval-and-compile
;; These are needed at compile time by p4-help-text.
(defun p4--get-process-environment ()
"Return a modified process environment for sub processes such
that p4 commands work as expected"
;; 1. Account for P4COLORS, e.g.
;; export P4COLORS="@info=0:@error=31;1:@warning=33;1:action=36:how:36:change=33:\
;; depotFile=32:path=32:location=32:rev=31:depotRev=31"
;; which cause commands like p4-opened to have ANSI control characters in the output that
;; isn't handle by Emacs (we do our own syntax highlighting). Therefore, we instruct p4 to
;; not produce ANSI escape codes when running p4 by setting P4COLORS to empty.
;;
;; 2. Account for P4DIFF set to an external diff tool which won't work when generating diff's
;; for use in Emacs.
(cons "P4DIFF=" (cons "P4COLORS=" process-environment)))
(defun p4-call-process (&optional infile destination display &rest args)
"Call Perforce synchronously in separate process.
The program to be executed is taken from `p4-executable'; INFILE,
DESTINATION, and DISPLAY are to be interpreted as for
`call-process'. The argument list ARGS is modified using
`p4-modify-args-function'."
(let ((process-environment (p4--get-process-environment)))
(apply #'call-process (p4-executable) infile destination display
(funcall p4-modify-args-function args)))))
;;; Macros (must be defined before use if compilation is to work)
(defmacro p4-with-temp-buffer (args &rest body)
"Run p4 ARGS in a temporary buffer.
Place point at the start of the output, and evaluate BODY
if the command completed successfully."
`(let ((dir (or p4-default-directory default-directory)))
(with-temp-buffer
(cd dir)
(when (zerop (p4-run ,args)) ,@body))))
(put 'p4-with-temp-buffer 'lisp-indent-function 1)
(defmacro p4-with-set-output (&rest body)
"Run p4 set in a temporary buffer.
Place point at the start of the output,
and evaluate BODY if the command completed successfully."
;; Can't use `p4-with-temp-buffer' for this, because that would lead
;; to infinite recursion via `p4-coding-system'.
`(let ((dir (or p4-default-directory default-directory)))
(with-temp-buffer
(cd dir)
(when (zerop (save-excursion
(p4-call-process nil t nil "set")))
,@body))))
(put 'p4-with-set-output 'lisp-indent-function 0)
(defmacro p4-with-coding-system (&rest body)
"Evaluate BODY using `p4-coding-system'.
This will evaluate BODY `coding-system-for-read' and
`coding-system-for-write' set to the result of
`p4-coding-system'."
`(let* ((coding (p4-coding-system))
(coding-system-for-read coding)
(coding-system-for-write coding))
,@body))
(put 'p4-with-coding-system 'lisp-indent-function 0)
;;; Environment:
(defun p4-version ()
"Describe the Emacs-Perforce Integration version."
(interactive)
(message "Emacs-P4 Integration version %s" p4-version))
(defvar p4-current-setting-cache (make-hash-table :test 'equal)
"Cached of result \"p4 set VAR\".")
(defun p4-current-setting-clear ()
"Clear (empty) the `p4-current-setting-cache' used by `p4-current-setting'."
(clrhash p4-current-setting-cache))
(defun p4-current-setting (var &optional default)
"Return the current Perforce client setting for VAR.
If VAR is not set, return DEFAULT. The client setting can come
from a .perforce file or the environment. The values are cached
to avoid repeated calls to p4 which can be slow."
(let* ((p4config (p4--get-p4-config)) ;; typically .perforce
(workspace-root (locate-dominating-file default-directory p4config))
(key (concat (format "%s : %s" var default)
(if workspace-root (concat " <" workspace-root p4config ">"))))
(ans (gethash key p4-current-setting-cache 'missing)))
(when (equal ans 'missing)
(setq ans (or (p4-with-set-output
(let ((re (format "^%s=\\(\\S-+\\)" (regexp-quote var))))
(when (re-search-forward re nil t)
(match-string 1))))
default))
(puthash key ans p4-current-setting-cache))
ans))
(defun p4--exists-in-p4-set-vars (var p4-set-vars)
"Does VAR=value exist in P4-SET-VARS?"
;; Would be nice to use
;; (seq-find (lambda (el) (string-match "^P4PORT=" el)) p4-set-vars))
;; instead of p4--exists-in-p4-set-vars ("P4PORT" p4-set-vars)
;; but seq-find isn't in emacs 24.
(let (ans
(el (car p4-set-vars)))
(while el
(if (string-match (concat "^" var "=") el)
(setq ans t
el nil)
(setq p4-set-vars (cdr p4-set-vars)
el (car p4-set-vars))))
ans))
(defun p4-current-environment ()
"Return Perforce process environment.
This is `process-environment' updated with the current Perforce client settings."
(let ((p4-set-vars (p4-with-set-output
(cl-loop while (re-search-forward "^P4[A-Z]+=\\S-+" nil t)
collect (match-string 0)))))
;; Default values for P4PORT and P4USER may be needed by
;; p4-password-source even if not supplied by "p4 set". See:
;; http://www.perforce.com/perforce/doc.current/manuals/cmdref/P4PORT.html
;; http://www.perforce.com/perforce/doc.current/manuals/cmdref/P4USER.html
(when (not (p4--exists-in-p4-set-vars "P4PORT" p4-set-vars))
(setq p4-set-vars (append p4-set-vars (list "P4PORT=perforce:1666"))))
(when (not (p4--exists-in-p4-set-vars "P4PORT" p4-set-vars))
(setq p4-set-vars (append p4-set-vars (list (concat "P4USER="
(or (getenv "USER")
(getenv "USERNAME")
(user-login-name)))))))
(append p4-set-vars process-environment)))
(defvar p4-coding-system-alist