This file is indexed.

/usr/share/emacs/site-lisp/mu4e/mu4e-headers.el is in mu4e 0.9.12-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   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
;;; mu4e-headers.el -- part of mu4e, the mu mail user agent
;;
;; Copyright (C) 2011-2014 Dirk-Jan C. Binnema

;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>

;; This file is not part of GNU Emacs.
;;
;; GNU Emacs 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.

;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; In this file are function related mu4e-headers-mode, to creating the list of
;; one-line descriptions of emails, aka 'headers' (not to be confused with
;; headers like 'To:' or 'Subject:')

;; Code:
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
(require 'cl)

(require 'fringe)
(require 'hl-line)

(require 'mu4e-utils)    ;; utility functions
(require 'mu4e-proc)
(require 'mu4e-vars)
(require 'mu4e-mark)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)

;; the headers view
(defgroup mu4e-headers nil
  "Settings for the headers view."
  :group 'mu4e)

(defcustom mu4e-headers-fields
  '( (:human-date    .   12)
     (:flags         .    6)
     (:mailing-list  .   10)
     (:from          .   22)
     (:subject       .   nil))
  "A list of header fields to show in the headers buffer.
Each element has the form (HEADER . WIDTH), where HEADER is one of
the available headers (see `mu4e-header-info') and WIDTH is the
respective width in characters.  A width of `nil' means
'unrestricted', and this is best reserved for the rightmost (last)
field. Note that emacs may become very slow with excessively long
lines (1000s of characters), so if you regularly get such messages,
you want to avoid fields with `nil' altogether."
  :type `(repeat (cons (choice ,@(mapcar (lambda (h)
					   (list 'const :tag
						 (plist-get (cdr h) :help)
						 (car h)))
					 mu4e-header-info))
		       (choice (integer :tag "width")
			       (const :tag "unrestricted width" nil))))
  :group 'mu4e-headers)

(defcustom mu4e-headers-date-format "%x"
  "Date format to use in the headers view.
In the format of `format-time-string'."
  :type  'string
  :group 'mu4e-headers)

(defcustom mu4e-headers-time-format "%X"
  "Time format to use in the headers view.
In the format of `format-time-string'."
  :type  'string
  :group 'mu4e-headers)

(defcustom mu4e-headers-long-date-format "%c"
  "Date format to use in the headers view tooltip.
In the format of `format-time-string'."
  :type  'string
  :group 'mu4e-headers)

(defcustom mu4e-headers-visible-lines 10
  "Number of lines to display in the header view when using the
horizontal split-view. This includes the header-line at the top,
and the mode-line."
  :type 'integer
  :group 'mu4e-headers)

(defcustom mu4e-headers-visible-columns 30
  "Number of columns to display for the header view when using the
vertical split-view."
  :type 'integer
  :group 'mu4e-headers)

(defcustom mu4e-headers-auto-update t
  "Whether to automatically update the current headers buffer if an
indexing operation showed changes."
  :type 'boolean
  :group 'mu4e-headers)

(defcustom mu4e-headers-results-limit 500
  "Maximum number of results to show; this affects performance
quite a bit, especially when `mu4e-headers-include-related' is
non-nil. Set to -1 for no limits, and you temporarily (for one
query) ignore the limit by pressing a C-u before invoking the
search."
  :type '(choice (const :tag "Unlimited" -1)
	   (integer :tag "Limit"))
  :group 'mu4e-headers)

(make-obsolete-variable 'mu4e-search-results-limit
  'mu4e-headers-results-limit "0.9.9.5-dev6")

(defcustom mu4e-headers-skip-duplicates nil
  "With this option set to non-nil, show only one of duplicate
messages. This is useful when you have multiple copies of the same
message, which is a common occurence for example when using Gmail
and offlineimap."
  :type 'boolean
  :group 'mu4e-headers)

(defcustom mu4e-headers-include-related nil
  "With this option set to non-nil, not just return the matches for
a searches, but also messages that are related (through their
references) to these messages. This can be useful e.g. to include
sent messages into message threads."
  :type 'boolean
  :group 'mu4e-headers)

(defcustom mu4e-headers-visible-flags
  '(draft flagged new passed replied seen trashed attach encrypted signed unread)
  "An ordered list of flags to show in the headers buffer. Each
element is a symbol in the list (DRAFT FLAGGED NEW PASSED
REPLIED SEEN TRASHED ATTACH ENCRYPTED SIGNED UNREAD)."
  :type '(set
          (const :tag "Draft" draft)
          (const :tag "Flagged" flagged)
          (const :tag "New" new)
          (const :tag "Passed" passed)
          (const :tag "Replied" replied)
          (const :tag "Seen" seen)
          (const :tag "Trashed" trashed)
          (const :tag "Attach" attach)
          (const :tag "Encrypted" encrypted)
          (const :tag "Signed" signed)
          (const :tag "Unread" unread))
  :group 'mu4e-headers)

(defcustom mu4e-headers-found-hook nil
  "Hook run just *after* all of the headers for the last search
query have been received and are displayed."
  :type 'hook
  :group 'mu4e-headers)


(defvar mu4e-headers-sort-field :date
  "Field to sort the headers by.
Field must be a symbol, one of: :date, :subject, :size, :prio,
:from, :to.")

(defvar mu4e-headers-sort-direction 'descending
  "Direction to sort by; a symbol either `descending' (sorting
  Z->A) or `ascending' (sorting A->Z).")


;; marks for headers of the form; each is a cons-cell (basic . fancy)
;; each of which is basic ascii char and something fancy, respectively
(defvar mu4e-headers-draft-mark     (purecopy '("D" . "⚒")) "Draft.")
(defvar mu4e-headers-flagged-mark   (purecopy '("F" . "⚑")) "Flagged.")
(defvar mu4e-headers-new-mark       (purecopy '("N" . "⭑")) "New.")
(defvar mu4e-headers-passed-mark    (purecopy '("P" . "❯")) "Passed (fwd).")
(defvar mu4e-headers-replied-mark   (purecopy '("R" . "❮")) "Replied.")
(defvar mu4e-headers-seen-mark      (purecopy '("S" . "✔")) "Seen.")
(defvar mu4e-headers-trashed-mark   (purecopy '("T" . "♻")) "Trashed.")
(defvar mu4e-headers-attach-mark    (purecopy '("a" . "⚓")) "W/ attachments.")
(defvar mu4e-headers-encrypted-mark (purecopy '("x" . "⚴")) "Encrypted.")
(defvar mu4e-headers-signed-mark    (purecopy '("s" . "☡")) "Signed.")
(defvar mu4e-headers-unread-mark    (purecopy '("u" . "☐")) "Unread.")

;; thread prefix marks
(defvar mu4e-headers-has-child-prefix    (purecopy '("+"  . "◼"))  "Parent.")
(defvar mu4e-headers-empty-parent-prefix (purecopy '("-"  . "◽"))  "Orphan.")
(defvar mu4e-headers-first-child-prefix  (purecopy '("\\" . "┗▶")) "First child.")
(defvar mu4e-headers-duplicate-prefix    (purecopy '("="  . "⚌"))  "Duplicate.")
(defvar mu4e-headers-default-prefix       (purecopy '("|"  . "┃")) "Default.")


(defvar mu4e-headers-actions
  '( ("capture message" . mu4e-action-capture-message))
  "List of actions to perform on messages in the headers list.
The actions are of the form (NAME SHORTCUT FUNC) where:
* NAME is the name of the action (e.g. \"Count lines\")
* SHORTCUT is a one-character shortcut to call this action
* FUNC is a function which receives a message plist as an argument.")

(defvar mu4e-headers-custom-markers
  '(("Older than"
      (lambda (msg date) (time-less-p (mu4e-msg-field msg :date) date))
      (lambda () (mu4e-get-time-date "Match messages before: ")))
     ("Newer than"
       (lambda (msg date) (time-less-p date (mu4e-msg-field msg :date)))
       (lambda () (mu4e-get-time-date "Match messages after: ")))
     ("Bigger than"
       (lambda (msg bytes) (> (mu4e-msg-field msg :size) (* 1024 bytes)))
       (lambda () (read-number "Match messages bigger than (Kbytes): "))))
  "List of custom markers -- functions to mark message that match
some custom function. Each of the list members has the following format:
  (NAME PREDICATE-FUNC PARAM-FUNC)
* NAME is the name of the predicate function, and the first character
is the shortcut (so keep those unique).
* PREDICATE-FUNC is a function that takes two parameters, MSG
and (optionally) PARAM, and should return non-nil when there's a
match.
* PARAM-FUNC is function that is evaluated once, and its value is then passed to
PREDICATE-FUNC as PARAM. This is useful for getting user-input.")

(defvar mu4e-headers-show-threads t
  "Whether to show threads in the headers list.")

(defvar mu4e-headers-full-search nil
  "Whether to show all results.
If this is nil show results up to `mu4e-search-results-limit')")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;; internal variables/constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; docid cookies
(defconst mu4e~headers-docid-pre (purecopy "\376")
  "Each header starts (invisibly) with the `mu4e~headers-docid-pre',
followed by the docid, followed by `mu4e~headers-docid-post'.")
(defconst mu4e~headers-docid-post (purecopy "\377")
  "Each header starts (invisibly) with the `mu4e~headers-docid-pre',
followed by the docid, followed by `mu4e~headers-docid-post'.")

(defvar mu4e~headers-view-win nil
  "The view window connected to this headers view.")

(defvar mu4e~headers-sort-field-choices
  '( ("date"	. :date)
     ("from"	. :from)
     ("prio"	. :prio)
     ("zsize"	. :size)
     ("subject"	. :subject)
     ("to"	. :to))
  "List of cells describing the various sort-options.
In the format needed for `mu4e-read-option'.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun mu4e~headers-clear ()
  "Clear the header buffer and related data structures."
  (when (buffer-live-p mu4e~headers-buffer)
    (let ((inhibit-read-only t))
      (with-current-buffer mu4e~headers-buffer
	(setq mu4e~view-msg nil)
	(mu4e~mark-clear)
	(erase-buffer)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; handler functions
;;
;; next are a bunch of handler functions; those will be called from mu4e~proc in
;; response to output from the server process

(defun mu4e~headers-view-handler (msg)
  "Handler function for displaying a message."
  (mu4e-view msg mu4e~headers-buffer))

(defun mu4e~headers-update-handler (msg is-move)
  "Update handler, will be called when a message has been updated
in the database. This function will update the current list of
headers."
  (when (buffer-live-p mu4e~headers-buffer)
    (with-current-buffer mu4e~headers-buffer
      (let* ((docid (mu4e-message-field msg :docid))
	     (initial-message-at-point (mu4e~headers-docid-at-point))
	     (initial-column (current-column))
	     (point (mu4e~headers-docid-pos docid)))
	(when point ;; is the message present in this list?

	  ;; if it's marked, unmark it now
	  (when (mu4e-mark-docid-marked-p docid)
	    (mu4e-mark-set 'unmark))

 	  ;; re-use the thread info from the old one; this is needed because
 	  ;; *update* messages don't have thread info by themselves (unlike
 	  ;; search results)
	  ;; since we still have the search results, re-use
 	  ;; those
 	  (plist-put msg :thread
 	    (mu4e~headers-field-for-docid docid :thread))

	  ;; first, remove the old one (otherwise, we'd have two headers with
	  ;; the same docid...
	  (mu4e~headers-remove-handler docid)

	  ;; if we we're actually viewing this message (in mu4e-view mode), we
	  ;; update it; that way, the flags can be updated, as well as the path
	  ;; (which is useful for viewing the raw message)
	  (let ((viewbuf (get-buffer mu4e~view-buffer-name)))
	    (when (and viewbuf (buffer-live-p viewbuf))
	      (with-current-buffer viewbuf
		(when (eq docid (plist-get mu4e~view-msg :docid))
		  (mu4e-view msg mu4e~headers-buffer)))))

	  ;; now, if this update was about *moving* a message, we don't show it
	  ;; anymore (of course, we cannot be sure if the message really no
	  ;; longer matches the query, but this seem a good heuristic.
	  ;; if it was only a flag-change, show the message with its updated flags.
	  (unless is-move
	    (mu4e~headers-header-handler msg point))

	  (if (and initial-message-at-point
		   (mu4e~headers-goto-docid initial-message-at-point))
	      (progn
		(move-to-column initial-column)
		(mu4e~headers-highlight initial-message-at-point))
	    ;; attempt to highlight the corresponding line and make it visible
	    (mu4e~headers-highlight docid))
	  )))))


(defun mu4e~headers-remove-handler (docid)
  "Remove handler, will be called when a message with DOCID has
been removed from the database. This function will hide the removed
message from the current list of headers. If the message is not
present, don't do anything."
  (when (buffer-live-p mu4e~headers-buffer)
    (with-current-buffer mu4e~headers-buffer
      (mu4e~headers-remove-header docid t))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defsubst mu4e~headers-contact-str (contacts)
  "Turn the list of contacts CONTACTS (with elements (NAME . EMAIL)
into a string."
  (mapconcat
    (lambda (ct)
      (let ((name (car ct)) (email (cdr ct)))
	(or name email "?"))) contacts ", "))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defsubst mu4e~headers-thread-prefix (thread)
  "Calculate the thread prefix based on thread info THREAD."
  (when thread
    (let ((get-prefix
	    (lambda (cell)  (if mu4e-use-fancy-chars (cdr cell) (car cell)))))
      (concat
	(make-string (* (if (plist-get thread :empty-parent) 0 1)
		 (plist-get thread :level)) ?\s)
	(cond
	  ((plist-get thread :has-child)
	    (funcall get-prefix mu4e-headers-has-child-prefix))
	  ((plist-get thread :empty-parent)
	    (funcall get-prefix mu4e-headers-empty-parent-prefix))
	  ((plist-get thread :first-child)
	    (funcall get-prefix mu4e-headers-first-child-prefix))
	  ((plist-get thread :duplicate)
	    (funcall get-prefix mu4e-headers-duplicate-prefix))
	  (t
	    (funcall get-prefix mu4e-headers-default-prefix)))
	" "))))

(defsubst mu4e~headers-flags-str (flags)
  "Get a display string for the flags.
Note that `mu4e-flags-to-string' is for internal use only; this
function is for display. (This difference is significant, since
internally, the Maildir spec determines what the flags look like,
while our display may be different)."
  (let ((str "")
        (get-prefix
         (lambda (cell) (if mu4e-use-fancy-chars (cdr cell) (car cell)))))
    (dolist (flag mu4e-headers-visible-flags)
      (when (member flag flags)
        (setq str
          (concat str
            (case flag
              ('draft     (funcall get-prefix mu4e-headers-draft-mark))
              ('flagged   (funcall get-prefix mu4e-headers-flagged-mark))
              ('new       (funcall get-prefix mu4e-headers-new-mark))
              ('passed    (funcall get-prefix mu4e-headers-passed-mark))
              ('replied   (funcall get-prefix mu4e-headers-replied-mark))
              ('seen      (funcall get-prefix mu4e-headers-seen-mark))
              ('trashed   (funcall get-prefix mu4e-headers-trashed-mark))
              ('attach    (funcall get-prefix mu4e-headers-attach-mark))
              ('encrypted (funcall get-prefix mu4e-headers-encrypted-mark))
              ('signed    (funcall get-prefix mu4e-headers-signed-mark))
              ('unread    (funcall get-prefix mu4e-headers-unread-mark)))))))
    str))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defconst mu4e-headers-from-or-to-prefix '("" . "To ")
  "Prefix for the :from-or-to field.
It's a cons cell with the car element being the From: prefix, the
cdr element the To: prefix.")

(defsubst mu4e~headers-from-or-to (msg)
  "When the from address for message MSG is one of the the user's addresses,
\(as per `mu4e-user-mail-address-list'), show the To address;
otherwise ; show the from address; prefixed with the appropriate
`mu4e-headers-from-or-to-prefix'."
  (let ((addr (cdr-safe (car-safe (mu4e-message-field msg :from)))))
    (if (mu4e-user-mail-address-p addr)
      (concat (cdr mu4e-headers-from-or-to-prefix)
	(mu4e~headers-contact-str (mu4e-message-field msg :to)))
      (concat (car mu4e-headers-from-or-to-prefix)
	(mu4e~headers-contact-str (mu4e-message-field msg :from))))))

(defsubst mu4e~headers-human-date (msg)
  "Show a 'human' date.
If the date is today, show the time, otherwise, show the
date. The formats used for date and time are
`mu4e-headers-date-format' and `mu4e-headers-time-format'."
  (let ((date (mu4e-msg-field msg :date)))
    (if (equal date '(0 0 0))
      "None"
      (let ((day1 (decode-time date))
	     (day2 (decode-time (current-time))))
	(if (and
	      (eq (nth 3 day1) (nth 3 day2))     ;; day
	      (eq (nth 4 day1) (nth 4 day2))     ;; month
	      (eq (nth 5 day1) (nth 5 day2)))    ;; year
	  (format-time-string mu4e-headers-time-format date)
	  (format-time-string mu4e-headers-date-format date))))))


(defsubst mu4e~headers-thread-subject (msg)
  "Get the subject if it is the first one in a thread; otherwise,
return the thread-prefix without the subject-text. In other words,
show the subject of a thread only once, similar to e.g. 'mutt'."
  (let* ((tinfo  (mu4e-message-field msg :thread))
	 (subj (mu4e-msg-field msg :subject)))
    (concat ;; prefix subject with a thread indicator
      (mu4e~headers-thread-prefix tinfo)
      (if (or (not tinfo) (zerop (plist-get tinfo :level))
	    (plist-get tinfo :empty-parent))
	(truncate-string-to-width subj 600) ""))))


(defsubst mu4e~headers-mailing-list (list)
  "Get some identifier for the mailing list."
  (if list
    (propertize (mu4e-get-mailing-list-shortname list) 'help-echo list)
    ""))

(defun mu4e~headers-custom-field (msg field)
  "Show some custom header field, or raise an error if it is not
found."
  (let* ((item (or (assoc field mu4e-header-info-custom)
		 (mu4e-error "field %S not found" field)))
	  (func (or (plist-get (cdr-safe item) :function)
		  (mu4e-error "no :function defined for field %S %S" field (cdr item)))))
    (funcall func msg)))

;; note: this function is very performance-sensitive
(defun mu4e~headers-header-handler (msg &optional point)
    "Create a one line description of MSG in this buffer, at POINT,
if provided, or at the end of the buffer otherwise."
    (let ((docid (mu4e-message-field msg :docid)) (line ""))
      (dolist (f-w mu4e-headers-fields)
	(let ((field (car f-w)) (width (cdr f-w))
	       (val (mu4e-message-field msg (car f-w))) (str))
	  (setq str
	    (case field
	      (:subject
		(concat ;; prefix subject with a thread indicator
		  (mu4e~headers-thread-prefix (mu4e-message-field msg :thread))
		  ;;  "["(plist-get (mu4e-message-field msg :thread) :path) "] "
		  
		  ;; work-around: emacs' display gets really slow when lines are too long;
		  ;; so limit subject length to 600 
		  (truncate-string-to-width val 600)))
	      (:thread-subject (mu4e~headers-thread-subject msg))
	      ((:maildir :path :message-id) val)
	      ((:to :from :cc :bcc) (mu4e~headers-contact-str val))
	      ;; if we (ie. `user-mail-address' is the 'From', show
	      ;; 'To', otherwise show From
	      (:from-or-to (mu4e~headers-from-or-to msg))
	      (:date (format-time-string mu4e-headers-date-format val))
	      (:mailing-list (mu4e~headers-mailing-list val))
	      (:human-date (propertize (mu4e~headers-human-date msg)
                                       'help-echo (format-time-string
                                                   mu4e-headers-long-date-format
                                                   (mu4e-msg-field msg :date))))
	      (:flags (propertize (mu4e~headers-flags-str val)
			'help-echo (format "%S" val)))
	      (:tags (propertize (mapconcat 'identity val ", ")))
	      (:size (mu4e-display-size val))
	      (t (mu4e~headers-custom-field msg field))))
	  (when str
	    (setq line
	      (concat line
		(if (not width)
		  str
		  (truncate-string-to-width str width 0 ?\s t)) " ")))))
      ;; now, propertize it.
      (setq line (propertize line 'face
		   (let ((flags (mu4e-message-field msg :flags)))
		     (cond
		       ((memq 'trashed flags) 'mu4e-trashed-face)
		       ((memq 'draft flags)   'mu4e-draft-face)
		       ((or
			  (memq 'unread flags)
			  (memq 'new flags))  'mu4e-unread-face)
		       ((memq 'flagged flags) 'mu4e-flagged-face)
		       ((memq 'replied flags) 'mu4e-replied-face)
		       ((memq 'passed flags)  'mu4e-forwarded-face)
		       (t                     'mu4e-header-face)))))
      ;; now, append the header line
      (mu4e~headers-add-header line docid point msg)))

(defconst mu4e~no-matches     (purecopy "No matching messages found"))
(defconst mu4e~end-of-results (purecopy "End of search results"))

(defun mu4e~headers-found-handler (count)
  "Create a one line description of the number of headers found
after the end of the search results."
  (when (buffer-live-p mu4e~headers-buffer)
    (with-current-buffer mu4e~headers-buffer
      (save-excursion
	(goto-char (point-max))
	(let ((inhibit-read-only t)
	       (str (if (zerop count) mu4e~no-matches mu4e~end-of-results)))
	  (insert (propertize str 'face 'mu4e-system-face 'intangible t))
	  (unless (zerop count)
	    (mu4e-message "Found %d matching message%s"
	      count (if (= 1 count) "" "s"))
	    ;; highlight the first message
	    (mu4e~headers-highlight (mu4e~headers-docid-at-point (point-min)))))
	;; run-hooks
	(run-hooks 'mu4e-headers-found-hook)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defmacro mu4e~headers-defun-mark-for (mark)
  "Define a function mu4e~headers-mark-MARK."
  (let ((funcname (intern (format "mu4e-headers-mark-for-%s" mark)))
	(docstring (format "Mark header at point with %s." mark)))
    `(progn
       (defun ,funcname () ,docstring
	 (interactive)
	 (mu4e-headers-mark-and-next ',mark))
       (put ',funcname 'definition-name ',mark))))

(mu4e~headers-defun-mark-for refile)
(mu4e~headers-defun-mark-for something)
(mu4e~headers-defun-mark-for delete)
(mu4e~headers-defun-mark-for flag)
(mu4e~headers-defun-mark-for move)
(mu4e~headers-defun-mark-for read)
(mu4e~headers-defun-mark-for trash)
(mu4e~headers-defun-mark-for unflag)
(mu4e~headers-defun-mark-for untrash)
(mu4e~headers-defun-mark-for unmark)
(mu4e~headers-defun-mark-for unread)


;;; headers-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar mu4e-headers-mode-map nil
  "Keymap for *mu4e-headers* buffers.")
(unless mu4e-headers-mode-map
  (setq mu4e-headers-mode-map
    (let ((map (make-sparse-keymap)))

      (define-key map  (kbd "C-S-u")   'mu4e-update-mail-and-index)
      ;; for terminal users
      (define-key map  (kbd "C-c C-u") 'mu4e-update-mail-and-index)

      (define-key map "s" 'mu4e-headers-search)
      (define-key map "S" 'mu4e-headers-search-edit)

      (define-key map "/" 'mu4e-headers-search-narrow)

      (define-key map "j" 'mu4e~headers-jump-to-maildir)

      (define-key map (kbd "<M-left>")  'mu4e-headers-query-prev)
      (define-key map (kbd "\\")        'mu4e-headers-query-prev)
      (define-key map (kbd "<M-right>") 'mu4e-headers-query-next)

      (define-key map "b" 'mu4e-headers-search-bookmark)
      (define-key map "B" 'mu4e-headers-search-bookmark-edit)

      (define-key map "O" 'mu4e-headers-change-sorting)
      (define-key map "P" 'mu4e-headers-toggle-threading)
      (define-key map "Q" 'mu4e-headers-toggle-full-search)
      (define-key map "W" 'mu4e-headers-toggle-include-related)
      (define-key map "V" 'mu4e-headers-toggle-skip-duplicates)

      (define-key map "q" 'mu4e~headers-quit-buffer)
      (define-key map "z" 'mu4e~headers-quit-buffer)

      (define-key map "g" 'mu4e-headers-rerun-search) ;; for compatibility

      (define-key map "%" 'mu4e-headers-mark-pattern)
      (define-key map "t" 'mu4e-headers-mark-subthread)
      (define-key map "T" 'mu4e-headers-mark-thread)

      ;; navigation between messages
      (define-key map "p" 'mu4e-headers-prev)
      (define-key map "n" 'mu4e-headers-next)
      (define-key map (kbd "<M-up>") 'mu4e-headers-prev)
      (define-key map (kbd "<M-down>") 'mu4e-headers-next)

      (define-key map (kbd "]") 'mu4e-headers-next-unread)
      (define-key map (kbd "[")
	(lambda() (interactive) (mu4e-headers-next-unread t)))
      
      ;; change the number of headers
      (define-key map (kbd "C-+") 'mu4e-headers-split-view-grow)
      (define-key map (kbd "C--") 'mu4e-headers-split-view-shrink)
      (define-key map (kbd "<C-kp-add>") 'mu4e-headers-split-view-grow)
      (define-key map (kbd "<C-kp-subtract>") 'mu4e-headers-split-view-shrink)


      ;; switching to view mode (if it's visible)
      (define-key map "y" 'mu4e-select-other-view)

      ;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      (define-key map (kbd "<backspace>")  'mu4e-headers-mark-for-trash)
      (define-key map (kbd "d")            'mu4e-headers-mark-for-trash)
      (define-key map (kbd "<delete>")     'mu4e-headers-mark-for-delete)
      (define-key map (kbd "<deletechar>") 'mu4e-headers-mark-for-delete)
      (define-key map (kbd "D")            'mu4e-headers-mark-for-delete)
      (define-key map (kbd "m")            'mu4e-headers-mark-for-move)
      (define-key map (kbd "r")            'mu4e-headers-mark-for-refile)

      (define-key map (kbd "?")            'mu4e-headers-mark-for-unread)
      (define-key map (kbd "!")            'mu4e-headers-mark-for-read)

      (define-key map (kbd "u")            'mu4e-headers-mark-for-unmark)
      (define-key map (kbd "+")            'mu4e-headers-mark-for-flag)
      (define-key map (kbd "-")            'mu4e-headers-mark-for-unflag)
      (define-key map (kbd "=")            'mu4e-headers-mark-for-untrash)
      (define-key map (kbd "&")            'mu4e-headers-mark-custom)

      (define-key map (kbd "*")              'mu4e-headers-mark-for-something)
      (define-key map (kbd "<kp-multiply>")  'mu4e-headers-mark-for-something)
      (define-key map (kbd "<insertchar>")   'mu4e-headers-mark-for-something)
      (define-key map (kbd "<insert>")       'mu4e-headers-mark-for-something)


      (define-key map (kbd "#")   'mu4e-mark-resolve-deferred-marks)

      (define-key map "U" 'mu4e-mark-unmark-all)
      (define-key map "x" 'mu4e-mark-execute-all)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

      (define-key map "a" 'mu4e-headers-action)

      ;; message composition
      (define-key map "R" 'mu4e-compose-reply)
      (define-key map "F" 'mu4e-compose-forward)
      (define-key map "C" 'mu4e-compose-new)
      (define-key map "E" 'mu4e-compose-edit)

      (define-key map (kbd "RET") 'mu4e-headers-view-message)
      (define-key map [mouse-2]   'mu4e-headers-view-message)

      (define-key map "$" 'mu4e-show-log)
      (define-key map "H" 'mu4e-display-manual)

      ;; menu
      (define-key map [menu-bar] (make-sparse-keymap))
      (let ((menumap (make-sparse-keymap "Headers")))
	(define-key map [menu-bar headers] (cons "Headers" menumap))

	(define-key menumap [mu4e~headers-quit-buffer]
	  '("Quit view" . mu4e~headers-quit-buffer))
	(define-key menumap [display-help] '("Help" . mu4e-display-manual))

	(define-key menumap [sepa0] '("--"))

	(define-key menumap [execute-marks]  '("Execute marks"
						. mu4e-mark-execute-all))
	(define-key menumap [unmark-all]  '("Unmark all" . mu4e-mark-unmark-all))
	(define-key menumap [unmark]      '("Unmark" . mu4e-headers-mark-for-unmark))

	(define-key menumap [mark-pattern]  '("Mark pattern" .
					       mu4e-headers-mark-pattern))
	(define-key menumap [mark-as-read]  '("Mark as read" .
					       mu4e-headers-mark-for-read))
	(define-key menumap [mark-as-unread]
	  '("Mark as unread" .  mu4e-headers-mark-for-unread))

	(define-key menumap [mark-delete]
	  '("Mark for deletion" . mu4e-headers-mark-for-delete))
	(define-key menumap [mark-trash]
	  '("Mark for trash" .  mu4e-headers-mark-for-trash))
	(define-key menumap [mark-move]
	  '("Mark for move" . mu4e-headers-mark-for-move))
	(define-key menumap [sepa1] '("--"))

	(define-key menumap [compose-new]  '("Compose new" . mu4e-compose-new))
	(define-key menumap [forward]  '("Forward" . mu4e-compose-forward))
	(define-key menumap [reply]  '("Reply" . mu4e-compose-reply))
	(define-key menumap [sepa2] '("--"))

	(define-key menumap [query-next]  '("Next query" . mu4e-headers-query-next))
	(define-key menumap [query-prev]  '("Previous query" .
					     mu4e-headers-query-prev))
	(define-key menumap [narrow-search] '("Narrow search" .
					       mu4e-headers-search-narrow))
	(define-key menumap [bookmark]  '("Search bookmark" .
					   mu4e-headers-search-bookmark))
	(define-key menumap [jump]  '("Jump to maildir" .
				       mu4e~headers-jump-to-maildir))
	(define-key menumap [refresh]  '("Refresh" . mu4e-headers-rerun-search))
	(define-key menumap [search]  '("Search" . mu4e-headers-search))


	(define-key menumap [sepa3] '("--"))

	(define-key menumap [view]  '("View" . mu4e-headers-view-message))
	(define-key menumap [next]  '("Next" . mu4e-headers-next))
	(define-key menumap [previous]  '("Previous" . mu4e-headers-prev))
	(define-key menumap [sepa4] '("--")))
      map)))
(fset 'mu4e-headers-mode-map mu4e-headers-mode-map)


(defun mu4e~header-line-format ()
  "Get the format for the header line."
  (cons
    (make-string
      (+ mu4e~mark-fringe-len (floor (fringe-columns 'left t))) ?\s)
    (mapcar
      (lambda (item)
	(let* ((field (car item)) (width (cdr item))
		(info (cdr (assoc field
			     (append mu4e-header-info mu4e-header-info-custom))))
		(sortable (plist-get info :sortable))
		(help (plist-get info :help))
		(uparrow   (if mu4e-use-fancy-chars " ▲" " ^"))
		(downarrow (if mu4e-use-fancy-chars " ▼" " V"))
		;; triangle to mark the sorted-by column
		(arrow
		  (when (and sortable (eq (car item) mu4e-headers-sort-field))
		    (if (eq mu4e-headers-sort-direction 'descending) downarrow uparrow)))
		(name (concat (plist-get info :shortname) arrow))
		(map (make-sparse-keymap)))
	  (when sortable
	    (define-key map [header-line mouse-1]
	      (lambda (&optional e)
		;; getting the field, inspired by `tabulated-list-col-sort'
		(interactive "e")
		(let* ((obj (posn-object (event-start e)))
			(field
			  (and obj (get-text-property 0 'field (car obj)))))
		  ;; "t": if we're already sorted by field, the sort-order is
		  ;; changed
		  (mu4e-headers-change-sorting field t)))))
	  (concat
	    (propertize
	      (if width
		(truncate-string-to-width name width 0 ?\s t)
		name)
	      'face (when arrow 'bold)
	      'help-echo help
	      'mouse-face (when sortable 'highlight)
	      'keymap (when sortable map)
	      'field field) " ")))
      mu4e-headers-fields)))

(defvar mu4e-headers-mode-abbrev-table nil)

(defun mu4e~headers-do-auto-update ()
  "Update the current headers buffer after indexing has brought
some changes, `mu4e-headers-auto-update' is non-nil and there is no
user-interaction ongoing."
  (when (and mu4e-headers-auto-update       ;; must be set
	  (zerop (mu4e-mark-marks-num))     ;; non active marks
	  (not (active-minibuffer-window))) ;; no user input
    (with-current-buffer mu4e~headers-buffer
      (mu4e-headers-rerun-search))))

(define-derived-mode mu4e-headers-mode special-mode
    "mu4e:headers"
  "Major mode for displaying mu4e search results.
\\{mu4e-headers-mode-map}."
  (use-local-map mu4e-headers-mode-map)
  (make-local-variable 'mu4e~headers-proc)
  (make-local-variable 'mu4e~highlighted-docid)
  (make-local-variable 'global-mode-string)
  (set (make-local-variable 'hl-line-face) 'mu4e-header-highlight-face)

  ;; maybe update the current headers upon indexing changes
  (add-hook 'mu4e-index-updated-hook 'mu4e~headers-do-auto-update nil t)
  (setq
    truncate-lines t
    buffer-undo-list t ;; don't record undo information
    overwrite-mode nil
    header-line-format (mu4e~header-line-format))

  (mu4e~mark-initialize) ;; initialize the marking subsystem
  (hl-line-mode 1))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; higlighting
(defvar mu4e~highlighted-docid nil
  "The highlighted docid")

(defun mu4e~headers-highlight (docid)
  "Highlight the header with DOCID, or do nothing if it's not found.
Also, unhighlight any previously highlighted headers."
  (with-current-buffer mu4e~headers-buffer
    (save-excursion
      ;; first, unhighlight the previously highlighted docid, if any
      (when (and mu4e~highlighted-docid
	      (mu4e~headers-goto-docid mu4e~highlighted-docid))
	(hl-line-unhighlight))
      ;; now, highlight the new one
      (when (mu4e~headers-goto-docid docid)
	(hl-line-highlight)))
    (setq mu4e~highlighted-docid docid)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mu4e~headers-select-window ()
  "When there is a visible window for the headers buffer, make sure
to select it. This is needed when adding new headers, otherwise
adding a lot of new headers looks really choppy."
  (let ((win (get-buffer-window mu4e~headers-buffer)))
    (when win (select-window win))))

;;;; headers in the buffer are prefixed by an invisible string with the docid
;;;; followed by an EOT ('end-of-transmission', \004, ^D) non-printable ascii
;;;; character. this string also has a text-property with the docid. the former
;;;; is used for quickly finding a certain header, the latter for retrieving the
;;;; docid at point without string matching etc.

(defsubst mu4e~headers-docid-cookie (docid)
  "Create an invisible string containing DOCID; this is to be used
at the beginning of lines to identify headers."
  (propertize (format "%s%d%s"
		mu4e~headers-docid-pre docid mu4e~headers-docid-post)
    'docid docid 'invisible t));;

(defsubst mu4e~headers-docid-at-point (&optional point)
  "Get the docid for the header at POINT, or at current (point) if
nil. Returns the docid, or nil if there is none."
    (save-excursion
      (when point
	(goto-char point))
      (get-text-property (line-beginning-position) 'docid)))

(defun mu4e~headers-goto-docid (docid &optional to-mark)
  "Go to the beginning of the line with the header with docid
DOCID, or nil if it cannot be found. If the optional TO-MARK is
non-nil, go to the point directly *after* the docid-cookie instead
of the beginning of the line."
  (let ((oldpoint (point)) (newpoint))
    (goto-char (point-min))
    (setq newpoint
      (search-forward (mu4e~headers-docid-cookie docid) nil t))
    (unless to-mark
      (if (null newpoint)
	(goto-char oldpoint) ;; not found; restore old pos
	(progn
	  (beginning-of-line) ;; found, move to beginning of line
	  (setq newpoint (point)))))
    newpoint)) ;; return the point, or nil if not found

(defsubst mu4e~headers-docid-pos (docid)
  "Return the pos of the beginning of the line with the header with
docid DOCID, or nil if it cannot be found."
  (let ((pos))
    (save-excursion
      (setq pos (mu4e~headers-goto-docid docid)))
    pos))

(defsubst mu4e~headers-field-for-docid (docid field)
  "Get FIELD (a symbol, see `mu4e-headers-names') for the message
with DOCID which must be present in the headers buffer."
  (save-excursion
    (when (mu4e~headers-goto-docid docid)
      (mu4e-message-field (mu4e-message-at-point) field))))

;;;; markers mark headers for
(defun mu4e~headers-mark (docid mark)
  "(Visually) mark the header for DOCID with character MARK."
  (with-current-buffer mu4e~headers-buffer
    (let ((inhibit-read-only t) (oldpoint (point)))
      (unless (mu4e~headers-goto-docid docid)
	(mu4e-error "Cannot find message with docid %S" docid))
      ;; now, we're at the beginning of the header, looking at
      ;; <docid>\004
      ;; (which is invisible). jump past that…
      (unless (re-search-forward mu4e~headers-docid-post nil t)
	(mu4e-error "Cannot find the `mu4e~headers-docid-post' separator"))

      ;; clear old marks, and add the new ones.
      (let ((msg (get-text-property (point) 'msg)))
	(delete-char mu4e~mark-fringe-len)
	(insert (propertize
		  (format mu4e~mark-fringe-format mark)
		  'face 'mu4e-header-marks-face
		  'docid docid
		  'msg msg)))
      (goto-char oldpoint))))


(defsubst mu4e~headers-add-header (str docid point &optional msg)
  "Add header STR with DOCID to the buffer at POINT if non-nil, or
at (point-max) otherwise. If MSG is not nil, add it as the
text-property `msg'."
  (when (buffer-live-p mu4e~headers-buffer)
    (with-current-buffer mu4e~headers-buffer
      (let ((inhibit-read-only t)
	     (is-first-header (= (point-min) (point-max))))
	(save-excursion
	  (goto-char (if point point (point-max)))
	  (insert
	    (propertize
	      (concat
		(mu4e~headers-docid-cookie docid)
		mu4e~mark-fringe
		str "\n")
	      'docid docid 'msg msg)))))))

(defun mu4e~headers-remove-header (docid &optional ignore-missing)
  "Remove header with DOCID at point.
When IGNORE-MISSING is non-nill, don't raise an error when the
docid is not found."
  (with-current-buffer mu4e~headers-buffer
    (if (mu4e~headers-goto-docid docid)
      (let ((inhibit-read-only t))
	(delete-region (line-beginning-position) (line-beginning-position 2)))
      (unless ignore-missing
	(mu4e-error "Cannot find message with docid %S" docid)))))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mu4e~headers-search-execute (expr ignore-history)
  "Search in the mu database for EXPR, and switch to the output
buffer for the results. If IGNORE-HISTORY is true, do *not* update
the query history stack."
  ;; note: we don't want to update the history if this query comes from
  ;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'.
  (mu4e-hide-other-mu4e-buffers)
  (let* ((buf (get-buffer-create mu4e~headers-buffer-name))
	 (inhibit-read-only t)
	  (maxnum (unless mu4e-headers-full-search mu4e-headers-results-limit)))
    (with-current-buffer buf
      (mu4e-headers-mode)
      (unless ignore-history
	;; save the old present query to the history list
	(when mu4e~headers-last-query
	  (mu4e~headers-push-query mu4e~headers-last-query 'past)))
      (setq
	mu4e~headers-buffer buf
	mode-name "mu4e-headers"
	mu4e~headers-last-query expr
	global-mode-string (propertize mu4e~headers-last-query
			     'face 'mu4e-modeline-face)))
    (switch-to-buffer buf)
    (mu4e~proc-find
      expr
      mu4e-headers-show-threads
      mu4e-headers-sort-field
      mu4e-headers-sort-direction
      maxnum
      mu4e-headers-skip-duplicates
      mu4e-headers-include-related)))

(defun mu4e~headers-redraw-get-view-window ()
  "Close all windows, redraw the headers buffer based on the value
of `mu4e-split-view', and return a window for the message view."
  (mu4e-hide-other-mu4e-buffers)
  (unless (buffer-live-p mu4e~headers-buffer)
    (mu4e-error "No headers buffer available"))
  (switch-to-buffer mu4e~headers-buffer)
  ;; kill the existing view win
  (when (buffer-live-p mu4e~view-buffer)
    (kill-buffer mu4e~view-buffer))
  ;; get a new view window
  (setq mu4e~headers-view-win
    (cond
      ((eq mu4e-split-view 'horizontal) ;; split horizontally
	(split-window-vertically mu4e-headers-visible-lines))
      ((eq mu4e-split-view 'vertical) ;; split vertically
	(split-window-horizontally mu4e-headers-visible-columns))
      (t ;; no splitting; just use the currently selected one
	(selected-window))))
  mu4e~headers-view-win)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; search-based marking

(defun mu4e-headers-for-each (func)
  "Call FUNC for each header, moving point to the header.
FUNC receives one argument, the message s-expression for the
corresponding header."
  (save-excursion
    (goto-char (point-min))
    (while (search-forward mu4e~headers-docid-pre nil t)
      ;; not really sure why we need to jump to bol; we do need to, otherwise we
      ;; miss lines sometimes...
      (let ((msg (get-text-property (line-beginning-position) 'msg)))
	(when msg
	  (funcall func msg))))))

(defun mu4e-headers-find-if (func &optional backward)
  "Move to the next header for which FUNC returns non-`nil',
starting from the current position. FUNC receives one argument, the
message s-expression for the corresponding header. If BACKWARD is
non-`nil', search backwards. Returns the new position, or `nil' if
nothing was found. If you want to exclude matches for the current
message, you can use `mu4e-headers-find-if-next'."
  (let ((pos)
	 (search-func (if backward 'search-backward 'search-forward)))
    (save-excursion
      (while (and (null pos)
	       (funcall search-func mu4e~headers-docid-pre nil t))
	;; not really sure why we need to jump to bol; we do need to, otherwise we
	;; miss lines sometimes...
	(let ((msg (get-text-property (line-beginning-position) 'msg)))
	  (when (and msg (funcall func msg))
	    (setq pos (point))))))
    (when pos
      (goto-char pos))))

(defun mu4e-headers-find-if-next (func &optional backwards)
  "Like `mu4e-headers-find-if', but do not match the current header.
Move to the next or (if BACKWARDS is non-`nil') header for which FUNC
returns non-`nil', starting from the current position."
  (let ((pos))
    (save-excursion
      (if backwards
	(beginning-of-line)
	(end-of-line))
      (setq pos (mu4e-headers-find-if func backwards)))
    (when pos (goto-char pos))))

(defvar mu4e~headers-regexp-hist nil
  "History list of regexps used.")

(defun mu4e-headers-mark-for-each-if (markpair mark-pred &optional param)
  "Mark all headers for which predicate function MARK-PRED returns
non-nil with MARKPAIR. MARK-PRED is function that receives two
arguments, MSG (the message at point) and PARAM (a user-specified
parameter). MARKPAIR is a cell (MARK . TARGET); see
`mu4e-mark-at-point' for details about marks."
  (mu4e-headers-for-each
    (lambda (msg)
      (when (funcall mark-pred msg param)
	(mu4e-mark-at-point (car markpair) (cdr markpair))))))

(defun mu4e-headers-mark-pattern ()
  "Ask user for a kind of mark (move, delete etc.), a field to
match and a regular expression to match with. Then, mark all
matching messages with that mark."
  (interactive)
  (let ((markpair (mu4e~mark-get-markpair "Mark matched messages with: " t))
	 (field (mu4e-read-option "Field to match: "
		  '( ("subject" . :subject)
		     ("from"    . :from)
		     ("to"      . :to))))
	  (pattern (read-string
		     (mu4e-format "Regexp:")
		     nil 'mu4e~headers-regexp-hist)))
    (mu4e-headers-mark-for-each-if
      markpair
      (lambda (msg param)
	(let* ((do-mark) (value (mu4e-msg-field msg field)))
	  (setq do-mark
	    (if (member field '(:to :from :cc :bcc :reply-to))
	      (find-if (lambda (contact)
			 (let ((name (car contact)) (email (cdr contact)))
			   (or (and name (string-match pattern name))
			     (and email (string-match pattern email))))) value)
	      (string-match pattern (or value "")))))))))

(defun mu4e-headers-mark-custom ()
  "Mark messages based on a user-provided predicate function."
  (interactive)
  (let* ((pred (mu4e-read-option "Match function: "
		 mu4e-headers-custom-markers))
	  (param (when (cdr pred) (eval (cdr pred))))
	  (markpair (mu4e~mark-get-markpair "Mark matched messages with: " t)))
    (mu4e-headers-mark-for-each-if markpair (car pred) param)))

(defun mu4e~headers-get-thread-info (msg what)
  "Get WHAT (a symbol, either path or thread-id) for MSG."
  (let* ((thread (or (mu4e-message-field msg :thread)
		   (mu4e-error "No thread info found")))
	  (path  (or (plist-get thread :path)
		   (mu4e-error "No threadpath found"))))
    (case what
      (path path)
      (thread-id
	(save-match-data
	  ;; the thread id is the first segment of the thread path
	  (when (string-match "^\\([[:xdigit:]]+\\):?" path)
	    (match-string 1 path))))
      (otherwise (mu4e-error "Not supported")))))


(defun mu4e-headers-mark-thread-using-markpair (markpair &optional subthread)
  "Mark the thread at point using the given markpair. If SUBTHREAD is
non-nil, marking is limited to the message at point and its
descendants."
  (let* ((mark (car markpair))
	 (allowed-marks (mapcar 'car mu4e-marks)))
    (unless (memq mark allowed-marks)
      (mu4e-error "The mark (%s) has to be one of: %s"
		  mark allowed-marks)))
  ;; note: the tread id is shared by all messages in a thread
  (let* ((msg (mu4e-message-at-point))
	 (thread-id (mu4e~headers-get-thread-info msg 'thread-id))
	 (path	   (mu4e~headers-get-thread-info msg 'path))
	 (last-marked-point))
    (mu4e-headers-for-each
      (lambda (mymsg)
 	(let ((my-thread-id (mu4e~headers-get-thread-info mymsg 'thread-id)))
	  (if subthread
	    ;; subthread matching; mymsg's thread path should have path as its
	    ;; prefix
	    (when (string-match (concat "^" path)
		    (mu4e~headers-get-thread-info mymsg 'path))
	      (mu4e-mark-at-point (car markpair) (cdr markpair))
	      (setq last-marked-point (point)))
	    ;; nope; not looking for the subthread; looking for the whole thread
	    (when (string= thread-id
		    (mu4e~headers-get-thread-info mymsg 'thread-id))
	      (mu4e-mark-at-point (car markpair) (cdr markpair))
	      (setq last-marked-point (point)))))))
    (when last-marked-point
      (goto-char last-marked-point)
      (mu4e-headers-next))))

(defun mu4e-headers-mark-thread (&optional subthread)
  "Like `mu4e-headers-mark-thread-using-markpair' but prompt for the markpair."
  (interactive "P")
  (let* (;; FIXME: e.g., for refiling we should evaluate this
	 ;; for each line separately
	 (markpair
	  (mu4e~mark-get-markpair
	   (if subthread "Mark subthread with: " "Mark whole thread with: ")
	   t)))
    (mu4e-headers-mark-thread-using-markpair markpair)))

(defun mu4e-headers-mark-subthread ()
  "Like `mu4e-mark-thread', but only for a sub-thread."
  (interactive)
  (mu4e-headers-mark-thread t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; the query past / present / future ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar mu4e~headers-query-past nil
  "Stack of queries before the present one.")
(defvar mu4e~headers-query-future nil
  "Stack of queries after the present one.")
(defvar mu4e~headers-query-stack-size 20
  "Maximum size for the query stacks.")

(defun mu4e~headers-push-query (query where)
  "Push QUERY to one of the query stacks.
WHERE is a symbol telling us where to push; it's a symbol, either
'future or 'past. Functional also removes duplicats, limits the
stack size."
  (let ((stack
	  (case where
	    (past   mu4e~headers-query-past)
	    (future mu4e~headers-query-future))))
     ;; only add if not the same item
    (unless (and stack (string= (car stack) query))
      (push query stack)
      ;; limit the stack to `mu4e~headers-query-stack-size' elements
      (when (> (length stack) mu4e~headers-query-stack-size)
	(setq stack (subseq stack 0 mu4e~headers-query-stack-size)))
      ;; remove all duplicates of the new element
      (remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
      ;; update the stacks
      (case where
	(past   (setq mu4e~headers-query-past   stack))
	(future (setq mu4e~headers-query-future stack))))))

(defun mu4e~headers-pop-query (whence)
  "Pop a query from the stack.
WHENCE is a symbol telling us where to get it from, either `future'
or `past'."
  (case whence
    (past
      (unless mu4e~headers-query-past
	(mu4e-warn "No more previous queries"))
      (pop mu4e~headers-query-past))
    (future
      (unless mu4e~headers-query-future
	(mu4e-warn "No more next queries"))
      (pop mu4e~headers-query-future))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; interactive functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar mu4e~headers-search-hist nil
  "History list of searches.")

(defun mu4e-headers-search (&optional expr prompt edit ignore-history)
  "Search in the mu database for EXPR, and switch to the output
buffer for the results. This is an interactive function which ask
user for EXPR. PROMPT, if non-nil, is the prompt used by this
function (default is \"Search for:\"). If EDIT is non-nil, instead
of executing the query for EXPR, let the user edit the query before
executing it. If IGNORE-HISTORY is true, do *not* update the query
history stack."
  ;; note: we don't want to update the history if this query comes from
  ;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'."
  (interactive)
  (let* ((prompt (mu4e-format (or prompt "Search for: ")))
	  (expr
	    (if edit
	      (read-string prompt expr)
	      (or expr
		(read-string prompt nil 'mu4e~headers-search-hist)))))
    (mu4e-mark-handle-when-leaving)
    (mu4e~headers-search-execute expr
      ignore-history)))

(defun mu4e-headers-search-edit ()
  "Edit the last search expression."
  (interactive)
  (mu4e-headers-search mu4e~headers-last-query nil t))

(defun mu4e-headers-search-bookmark (&optional expr edit)
  "Search using some bookmarked query EXPR.
If EDIT is non-nil, let the user edit the bookmark before starting
the search."
  (interactive)
  (let ((expr
	  (or expr
	    (mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
    (mu4e-headers-search expr (when edit "Edit bookmark: ") edit)))

(defun mu4e-headers-search-bookmark-edit ()
  "Edit an existing bookmark before executing it."
  (interactive)
  (mu4e-headers-search-bookmark nil t))


(defun mu4e-headers-search-narrow (filter )
  "Narrow the last search by appending search expression FILTER to
the last search expression. Note that you can go back to previous
query (effectively, 'widen' it), with `mu4e-headers-query-prev'."
  (interactive
    (let ((filter
  	    (read-string (mu4e-format "Narrow down to: ")
  	      nil 'mu4e~headers-search-hist nil t)))
      (list filter)))
  (unless mu4e~headers-last-query
    (mu4e-warn "There's nothing to filter"))
  (mu4e-headers-search
    (format "(%s) AND %s" mu4e~headers-last-query filter)))


(defun mu4e-headers-change-sorting (&optional field dir)
  "Change the sorting/threading parameters.
FIELD is the field to sort by; DIR is a symbol: either 'ascending,
'descending, 't (meaning: if FIELD is the same as the current
sortfield, change the sort-order) or nil (ask the user)."
  (interactive)
  (let* ((field
	   (or field
	     (mu4e-read-option "Sortfield: " mu4e~headers-sort-field-choices)))
	  ;; note: 'sortable' is either a boolean (meaning: if non-nil, this is
	  ;; sortable field), _or_ another field (meaning: sort by this other field).
	  (sortable (plist-get (cdr (assoc field mu4e-header-info)) :sortable))
	  ;; error check
	  (sortable
	    (if sortable
	      sortable
	      (mu4e-error "Not a sortable field")))
	  (sortfield (if (booleanp sortable) field sortable))
	  (dir
	    (case dir
	      ((ascending descending) dir)
	      ;; change the sort order if field = curfield
	      (t
		(if (eq sortfield mu4e-headers-sort-field)
		  (if (eq mu4e-headers-sort-direction 'ascending)
		      'descending 'ascending)
		  'descending))
	      (mu4e-read-option "Direction: "
		'(("ascending" . 'ascending) ("descending" . 'descending))))))
    (setq
      mu4e-headers-sort-field sortfield
      mu4e-headers-sort-direction dir)
    (mu4e-message "Sorting by %s (%s)"
      (symbol-name sortfield)
      (symbol-name mu4e-headers-sort-direction))
    (mu4e-headers-rerun-search)))

(defun mu4e~headers-toggle (name togglevar dont-refresh)
  "Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
re-run the last search."
  (set togglevar (not (symbol-value togglevar)))
  (mu4e-message "%s turned %s%s"
    name
    (if (symbol-value togglevar) "on" "off")
    (if dont-refresh
      " (press 'g' to refresh)" ""))
  (unless dont-refresh
    (mu4e-headers-rerun-search)))

(defun mu4e-headers-toggle-threading (&optional dont-refresh)
  "Toggle `mu4e-headers-show-threads'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
  (interactive "P")
  (mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))

(defun mu4e-headers-toggle-full-search (&optional dont-refresh)
  "Toggle `mu4e-headers-full-search'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
  (interactive "P")
  (mu4e~headers-toggle "Full-search"
    'mu4e-headers-full-search dont-refresh))

(defun mu4e-headers-toggle-include-related (&optional dont-refresh)
  "Toggle `mu4e-headers-include-related'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
  (interactive "P")
  (mu4e~headers-toggle "Include-related"
    'mu4e-headers-include-related dont-refresh))

(defun mu4e-headers-toggle-skip-duplicates (&optional dont-refresh)
  "Toggle `mu4e-headers-skip-duplicates'. With prefix-argument, do
_not_ refresh the last search with the new setting for threading."
  (interactive "P")
  (mu4e~headers-toggle "Skip-duplicates"
  'mu4e-headers-skip-duplicates dont-refresh))

(defvar mu4e~headers-loading-buf nil
  "A buffer for loading a message view.")

(defun mu4e~headers-get-loading-buf ()
  "Get a buffer to give feedback while loading a message view."
  (unless (buffer-live-p mu4e~headers-loading-buf)
    (setq mu4e~headers-loading-buf
      (get-buffer-create " *mu4e-loading*")))
  (with-current-buffer mu4e~headers-loading-buf
      (let ((inhibit-read-only t))
	(erase-buffer)
	(local-set-key (kbd "q") 'kill-buffer-and-window)
	(insert (propertize "Waiting for message..."
		  'face 'mu4e-system-face 'intangible t))))
  mu4e~headers-loading-buf)

(defun mu4e-headers-view-message ()
  "View message at point.
If there's an existing window for the view, re-use that one. If
not, create a new one, depending on the value of
`mu4e-split-view': if it's a symbol `horizontal' or `vertical',
split the window accordingly; if it is nil, replace the current
window. "
  (interactive)
  (unless (eq major-mode 'mu4e-headers-mode)
    (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
  (let* ((msg (mu4e-message-at-point))
	  (docid (or (mu4e-message-field msg :docid)
		   (mu4e-warn "No message at point")))
	  ;; decrypt (or not), based on `mu4e-decryption-policy'.
	  (decrypt
	    (and (member 'encrypted (mu4e-message-field msg :flags))
	      (if (eq mu4e-decryption-policy 'ask)
		(yes-or-no-p (mu4e-format "Decrypt message?"))
		mu4e-decryption-policy)))
	  (viewwin (mu4e~headers-redraw-get-view-window)))
    (unless (window-live-p viewwin)
      (mu4e-error "Cannot get a message view"))
    (select-window viewwin)
    (switch-to-buffer (mu4e~headers-get-loading-buf))
    (mu4e~proc-view docid mu4e-view-show-images decrypt)))

(defun mu4e-headers-rerun-search ()
  "Rerun the search for the last search expression."
  (interactive)
  (mu4e-headers-search mu4e~headers-last-query))

(defun mu4e~headers-query-navigate (whence)
  "Execute the previous query from the query stacks.
WHENCE determines where the query is taken from and is a symbol,
either `future' or `past'."
  (let ((query (mu4e~headers-pop-query whence))
	 (where (if (eq whence 'future) 'past 'future)))
    (when query
      (mu4e~headers-push-query mu4e~headers-last-query where)
      (mu4e-headers-search query nil nil t))))

(defun mu4e-headers-query-next ()
  "Execute the previous query from the query stacks."
  (interactive)
  (mu4e~headers-query-navigate 'future))

(defun mu4e-headers-query-prev ()
  "Execute the previous query from the query stacks."
  (interactive)
  (mu4e~headers-query-navigate 'past))

;; forget the past so we don't repeat it :/
(defun mu4e-headers-forget-queries ()
  "Forget all the complete query history."
  (interactive)
  (setq
    ;; note: don't forget the present one
    mu4e~headers-query-past nil
    mu4e~headers-query-future nil)
  (mu4e-message "Query history cleared"))

(defun mu4e~headers-move (lines)
  "Move point LINES lines forward (if LINES is positive) or
backward (if LINES is negative). If this succeeds, return the new
docid. Otherwise, return nil."
  (unless (eq major-mode 'mu4e-headers-mode)
    (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
  (let ((succeeded (zerop (forward-line lines)))
	 (docid (mu4e~headers-docid-at-point)))
    ;; move point, even if this function is called when this window is not
    ;; visible
    (when docid
      ;; update all windows showing the headers buffer
      (walk-windows
	(lambda (win)
	  (when (eq (window-buffer win) mu4e~headers-buffer)
	    (set-window-point win (point))))
	nil t)
       ;;(set-window-point (get-buffer-window mu4e~headers-buffer t) (point))
      ;; attempt to highlight the new line, display the message
      (mu4e~headers-highlight docid)
      ;; update message view if it was already showing
      (when (and mu4e-split-view (window-live-p mu4e~headers-view-win))
	(mu4e-headers-view-message))
      docid)))

(defun mu4e-headers-next (&optional n)
  "Move point to the next message header.
If this succeeds, return the new docid. Otherwise, return nil.
Optionally, takes an integer N (prefix argument), to the Nth next
header."
  (interactive "P")
  (mu4e~headers-move (or n 1)))

(defun mu4e-headers-prev (&optional n)
  "Move point to the previous message header.
If this succeeds, return the new docid. Otherwise, return nil.
Optionally, takes an integer N (prefix argument), to the Nth
previous header."
  (interactive "P")
  (mu4e~headers-move (- (or n 1))))

(defun mu4e-headers-next-unread (&optional backwards)
  "Move point to the next message that is unread (and
untrashed). If BACKWARDS is non-`nil', move backwards."
  (interactive "P")
  (or (mu4e-headers-find-if-next
	(lambda (msg)
	  (let ((flags (mu4e-message-field msg :flags))) 
	    (and (member 'unread flags) (not (member 'trashed flags)))))
	backwards)
    (mu4e-message (format "No %s unread message found"
		    (if backwards "previous" "next")))))


(defun mu4e~headers-jump-to-maildir (maildir)
  "Show the messages in maildir (user is prompted to ask what
maildir)."
  (interactive
    (let ((maildir (mu4e-ask-maildir "Jump to maildir: ")))
      (list maildir)))
  (when maildir
    (mu4e-mark-handle-when-leaving)
    (mu4e-headers-search
      (format "maildir:\"%s\"" maildir))))

(defun mu4e-headers-split-view-grow (&optional n)
  "In split-view, grow the headers window.
In horizontal split-view, increase the number of lines shown by N.
In vertical split-view, increase the number of columns shown by N.
If N is negative shrink the headers window.  When not in split-view
do nothing."
  (interactive "P")
  (let ((n (or n 1))
	 (hwin (get-buffer-window mu4e~headers-buffer)))
  (when (and (buffer-live-p mu4e~view-buffer) (window-live-p hwin))
     (let ((n (or n 1)))
       (case mu4e-split-view
	 ;; emacs has weird ideas about what horizontal, vertical means...
	 (horizontal
	   (window-resize hwin n nil)
	   (incf mu4e-headers-visible-lines n))
	 (vertical
	   (window-resize hwin n t)
	   (incf mu4e-headers-visible-columns n)))))))

(defun mu4e-headers-split-view-shrink (&optional n)
  "In split-view, shrink the headers window.
In horizontal split-view, decrease the number of lines shown by N.
In vertical split-view, decrease the number of columns shown by N.
If N is negative grow the headers window.  When not in split-view
do nothing."
  (interactive "P")
  (mu4e-headers-split-view-grow (- (or n 1))))

(defun mu4e-headers-action ()
  "Ask user what to do with message-at-point, then do it.
The actions are specified in `mu4e-headers-actions'."
  (interactive)
  (let ((msg (mu4e-message-at-point))
	 (actionfunc (mu4e-read-option "Action: " mu4e-headers-actions)))
    (funcall actionfunc msg)))

(defun mu4e-headers-mark-and-next (mark)
  "Set mark MARK on the message at point or on all messages in the
region if there is a region, then move to the next message."
  (interactive)
  (mu4e-mark-set mark)
  (mu4e-headers-next))

(defun mu4e~headers-quit-buffer ()
  "Quit the mu4e-headers buffer.
This is a rather complex function, to ensure we don't disturb
other windows."
  (interactive)
  (unless (eq major-mode 'mu4e-headers-mode)
    (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
  (mu4e-mark-handle-when-leaving)
  (let ((curbuf (current-buffer)) (curwin (selected-window))
	 (headers-visible))
    (walk-windows
      (lambda (win)
	(with-selected-window win
	  ;; if we the view window connected to this one, kill it
	  (when (and (not (one-window-p win)) (eq mu4e~headers-view-win win))
	    (delete-window win)
	    (setq mu4e~headers-view-win nil)))
	;; and kill any _other_ (non-selected) window that shows the current
	;; buffer
	(when (and
		(eq curbuf (window-buffer win)) ;; does win show curbuf?
		(not (eq curwin win))	        ;; it's not the curwin?
		(not (one-window-p)))           ;; and not the last one?
	  (delete-window win))))  ;; delete it!
    ;; now, all *other* windows should be gone. kill ourselves, and return
    ;; to the main view
    (kill-buffer)
    (mu4e~main-view)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(provide 'mu4e-headers)