OSDN Git Service

fix incomplete font-lock after save
[howm/howm.git] / howm-view.el
1 ;;; howm-view.el --- Wiki-like note-taking tool
2 ;;; Copyright (C) 2002, 2003, 2004, 2005-2020
3 ;;;   HIRAOKA Kazuyuki <khi@users.osdn.me>
4 ;;;
5 ;;; This program is free software; you can redistribute it and/or modify
6 ;;; it under the terms of the GNU General Public License as published by
7 ;;; the Free Software Foundation; either version 1, or (at your option)
8 ;;; any later version.
9 ;;;
10 ;;; This program is distributed in the hope that it will be useful,
11 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;;; GNU General Public License for more details.
14 ;;;
15 ;;; The GNU General Public License is available by anonymouse ftp from
16 ;;; prep.ai.mit.edu in pub/gnu/COPYING.  Alternately, you can write to
17 ;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
18 ;;; USA.
19 ;;--------------------------------------------------------------------
20
21 (provide 'howm-view)
22 (require 'howm)
23
24 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25 ;;; variables
26
27 ;; customize
28 (defvar howm-view-summary-sep "|")
29 (defvar howm-view-summary-format
30   (let* ((path (format-time-string howm-file-name-format))
31          (width (length (file-name-nondirectory path))))
32     (concat "%-" (format "%s" (1+ width)) "s" howm-view-summary-sep " ")))
33 (defvar howm-view-header-format
34   "\n==========================>>> %s\n"
35   "Format string of header for howm-view-contents.
36 %s is replaced with file name. See `format'.")
37 (defvar howm-view-header-regexp "^==========================>>> .*$")
38 (defvar howm-view-open-recenter howm-view-search-recenter)
39 (defvar howm-view-title-header "=")
40 ;; howm-view-title-regexp is assumed to have a form "^xxxxxxx$"
41 (defvar howm-view-title-regexp (format "^%s\\( +\\(.*\\)\\|\\)$"
42                                      (regexp-quote howm-view-title-header)))
43 (defvar howm-view-title-regexp-pos 2)
44 (defvar howm-view-title-regexp-grep (format "^%s +"
45                                      (regexp-quote howm-view-title-header)))
46 (defun howm-view-title-regexp-grep ()
47   (if howm-view-use-grep
48       howm-view-title-regexp-grep
49     howm-view-title-regexp))
50
51 (howm-defvar-risky howm-view-sort-methods
52   '(("random" . howm-view-sort-by-random)
53     ("name" . howm-view-sort-by-name)
54     ("name-match" . howm-view-lift-by-name)
55     ("numerical-name" . howm-view-sort-by-numerical-name)
56     ("summary" . howm-view-sort-by-summary)
57     ("summary-match" . howm-view-lift-by-summary)
58     ("summary-match-string" . howm-view-lift-by-summary-substring)
59 ;     ("atime" . howm-view-sort-by-atime) ;; nonsense
60 ;     ("ctime" . howm-view-sort-by-ctime) ;; needless
61     ("mtime" . howm-view-sort-by-mtime)
62     ("date" . howm-view-sort-by-reverse-date)
63     ("reminder" . howm-view-sort-by-reminder)
64     ("reverse" . howm-view-sort-reverse)))
65
66 (howm-defvar-risky howm-view-filter-methods
67   '(("name" . howm-view-filter-by-name)
68     ("summary" . howm-view-filter-by-summary)
69     ("mtime" . howm-view-filter-by-mtime)
70 ;     ("ctime" . howm-view-filter-by-ctime) ;; needless
71     ("date" . howm-view-filter-by-date)
72     ("reminder" . howm-view-filter-by-reminder)
73     ("contents" . howm-view-filter-by-contents)
74     ("Region" . howm-view-filter-by-region)
75     ("Around" . howm-view-filter-by-around)
76 ;     ("uniq" . howm-view-filter-uniq))
77   ))
78
79 ;; referred only when howm-view-use-grep is nil
80 (defvar howm-view-watch-modified-buffer t)
81
82 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
83 ;;; item
84
85 (defun howm-view-item-basename (item &optional nonempty)
86   (let* ((f (howm-item-name item))
87          (b (file-name-nondirectory f)))
88     (if (and (string= b "") nonempty)
89         f
90       b)))
91
92 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
93 ;;; riffle
94
95 (defalias 'riffle-home:howm              'howm-view-item-home)
96 (defalias 'riffle-summary-item:howm      'howm-view-summary-item)
97 (defalias 'riffle-contents-item:howm     'howm-view-contents-item)
98 (defalias 'riffle-summary-set-mode:howm  'howm-view-summary-mode)
99 (defalias 'riffle-contents-set-mode:howm 'howm-view-contents-mode)
100
101 (defun riffle-summary-name-format:howm ()
102   howm-view-summary-name)
103 (defun riffle-contents-name-format:howm ()
104   howm-view-contents-name)
105 (defun riffle-post-update:howm (item)
106   (howm-message-nolog "View: %s" (howm-view-item-filename item)))
107
108 ;;; aliases
109
110 ;; Only howm-view.el should call riffle-xxx.
111 ;; Define alias if it is used in howm-xxx besides howm-view.el.
112 (defalias 'howm-view-name          #'riffle-name)          
113 (defalias 'howm-view-item-list     #'riffle-item-list)     
114 (defalias 'howm-view-line-number   #'riffle-line-number)   
115 (defalias 'howm-view-summary-check #'riffle-summary-check) 
116 (defalias 'howm-view-persistent-p  #'riffle-persistent-p)  
117 (defalias 'howm-view-kill-buffer   #'riffle-kill-buffer)   
118 (defalias 'howm-view-set-place     #'riffle-set-place)     
119 (defalias 'howm-view-get-place     #'riffle-get-place)     
120 (defalias 'howm-view-summary-current-item  #'riffle-summary-current-item)
121 (defalias 'howm-view-contents-current-item #'riffle-contents-current-item)
122 (defalias 'howm-view-summary-to-contents   #'riffle-summary-to-contents)
123 (defalias 'howm-view-restore-window-configuration #'riffle-restore-window-configuration)
124
125 ;; for howmoney.el
126 ;; https://howm.osdn.jp/cgi-bin/hiki/hiki.cgi?howmoney
127 (defun howm-view-get-buffer (name-format &optional name new)
128   (let ((riffle-type ':howm)) ;; cheat
129     (riffle-get-buffer name-format name new)))
130 (defun howm-view-summary-buffer (&optional new)
131   (let ((riffle-type ':howm)) ;; cheat
132     (riffle-summary-buffer new)))
133 (defalias 'howm-view-summary-show 'riffle-summary-show)
134 (defalias 'howm-view-set-item-list 'riffle-set-item-list)
135
136 ;; for howmz
137 ;; http://noir.s7.xrea.com/archives/000136.html
138 ;; http://noir.s7.xrea.com/pub/zaurus/howmz.el
139 (defalias 'howm-view-sort-items 'howm-sort)
140
141 ;;; variables
142
143 (defvar howm-view-font-lock-silent t
144   "Inhibit font-lock-verbose if non-nil.")
145 (howm-defvar-risky howm-view-summary-font-lock-keywords
146   `((,(concat "\\(^[^ \t\r\n].*?\\)" (regexp-quote howm-view-summary-sep))
147      1 howm-view-name-face)
148     ("^ +" . howm-view-empty-face)))
149 (howm-defvar-risky howm-view-contents-font-lock-keywords nil)
150
151 (howm-defvar-risky *howm-view-font-lock-keywords* nil
152   "For internal use. Don't set this variable.
153 This is a shameful global variable and should be clearned in future.")
154 (howm-defvar-risky howm-view-font-lock-keywords nil
155   "For internal use.")
156 (defvar howm-view-font-lock-first-time t
157   "For internal use.")
158 (make-variable-buffer-local 'howm-view-font-lock-keywords)
159 (make-variable-buffer-local 'howm-view-font-lock-first-time)
160
161 ;;; modes
162
163 (riffle-define-derived-mode howm-view-summary-mode riffle-summary-mode "HowmS"
164   "memo viewer (summary mode)
165 key     binding
166 ---     -------
167 \\[howm-view-summary-open]      Open file
168 \\[next-line]   Next item
169 \\[previous-line]       Previous item
170 \\[riffle-pop-or-scroll-other-window]   Pop and scroll contents
171 \\[scroll-other-window-down]    Scroll contents
172 \\[riffle-scroll-other-window]  Scroll contents one line
173 \\[riffle-scroll-other-window-down]     Scroll contents one line
174 \\[riffle-summary-to-contents]  Concatenate all contents
175 \\[howm-view-filter-uniq]       Remove duplication of same file
176 \\[howm-view-summary-shell-command]     Execute command in inferior shell
177
178 \\[delete-other-windows]        Delete contents window
179 \\[riffle-pop-window]   Pop contents window
180 \\[riffle-toggle-window]        Toggle contents window
181 \\[howm-list-toggle-title]      Show/Hide Title
182
183 \\[howm-view-filter]    Filter (by date, contents, etc.)
184 \\[howm-view-filter-by-contents]        Search (= filter by contents)
185 \\[howm-view-sort]      Sort (by date, summary line, etc.)
186 \\[howm-view-sort-reverse]      Reverse order
187 \\[howm-view-dired]     Invoke Dired-X
188 \\[describe-mode]       This help
189 \\[riffle-kill-buffer]  Quit
190 "
191   (make-local-variable 'font-lock-keywords)
192   (cheat-font-lock-mode howm-view-font-lock-silent)
193   (when howm-view-font-lock-first-time
194     (setq howm-view-font-lock-first-time nil)
195     (cheat-font-lock-merge-keywords howm-user-font-lock-keywords
196                                     howm-view-summary-font-lock-keywords
197                                     ;; dirty! Clean dependency between files.
198                                     (howm-reminder-today-font-lock-keywords)))
199   (when *howm-view-font-lock-keywords*
200     (setq howm-view-font-lock-keywords *howm-view-font-lock-keywords*))
201   (when howm-view-font-lock-keywords
202     (cheat-font-lock-merge-keywords howm-view-font-lock-keywords
203                                     howm-user-font-lock-keywords
204                                     howm-view-summary-font-lock-keywords))
205   ;; font-lock-set-defaults removes these local variables after 2008-02-24
206   (set (make-local-variable 'font-lock-keywords-only) t)
207   (set (make-local-variable 'font-lock-keywords-case-fold-search) t)
208   ;;     (setq font-lock-keywords-case-fold-search
209   ;;           howm-view-grep-ignore-case-option)
210   (cheat-font-lock-fontify)
211   )
212
213 (riffle-define-derived-mode howm-view-contents-mode riffle-contents-mode "HowmC"
214   "memo viewer (contents mode)
215 key     binding
216 ---     -------
217 \\[howm-view-contents-open]     Open file
218 \\[next-line]   Next line
219 \\[previous-line]       Previous line
220 \\[scroll-up]   Scroll up
221 \\[scroll-down] Scroll down
222 \\[riffle-scroll-up]    Scroll one line up
223 \\[riffle-scroll-down]  Scroll one line down
224 \\[riffle-contents-to-summary]  Summary
225 \\[riffle-contents-goto-next-item]      Next item
226 \\[riffle-contents-goto-previous-item]  Previous item
227
228 \\[howm-view-filter]    Filter (by date, contents, etc.)
229 \\[howm-view-filter-by-contents]        Search (= filter by contents)
230 \\[howm-view-sort]      Sort
231 \\[howm-view-sort-reverse]      Reverse order
232 \\[howm-view-dired]     Invoke Dired-X
233 \\[describe-mode]       This help
234 \\[riffle-kill-buffer]  Quit
235 "
236 ;   (kill-all-local-variables)
237   (make-local-variable 'font-lock-keywords)
238   (cheat-font-lock-mode howm-view-font-lock-silent)
239   (let ((ck `((,howm-view-header-regexp (0 howm-view-hilit-face))))
240         (sk (or (howm-view-font-lock-keywords)
241                 *howm-view-font-lock-keywords*)))
242 ;;         ;; extremely dirty!! [2003/10/06 21:08]
243 ;;         (sk (or (with-current-buffer (riffle-summary-buffer)
244 ;;                   font-lock-keywords)
245 ;;                 *howm-view-font-lock-keywords*)))
246     (cheat-font-lock-merge-keywords sk ck
247                                     howm-user-font-lock-keywords
248                                     howm-view-contents-font-lock-keywords)
249     ;; font-lock-set-defaults removes these local variables after 2008-02-24
250     (set (make-local-variable 'font-lock-keywords-only) t)
251     (set (make-local-variable 'font-lock-keywords-case-fold-search)
252          howm-view-grep-ignore-case-option)
253     (cheat-font-lock-fontify)
254     ))
255
256 (defun howm-view-font-lock-keywords ()
257   (with-current-buffer (riffle-summary-buffer)
258     howm-view-font-lock-keywords))
259
260 ;;; keymaps
261
262 ;; (defvar howm-view-summary-mode-map nil)
263 ;; (defvar howm-view-contents-mode-map nil)
264
265 (defun howm-view-define-common-key (keymap)
266   (let ((m keymap))
267 ;;     (define-key m "?" 'howm-view-help)
268     (define-key m "f" 'howm-view-filter)
269     (define-key m "G" 'howm-view-filter-by-contents)
270     (define-key m "S" 'howm-view-sort)
271     (define-key m "R" 'howm-view-sort-reverse)
272     (define-key m "q" 'howm-view-kill-buffer)
273     (define-key m "X" 'howm-view-dired)
274     ))
275
276 (let ((m howm-view-summary-mode-map))
277   (define-key m "\C-m" 'howm-view-summary-open)
278   (define-key m "\C-j" 'howm-view-summary-open)
279   (define-key m "u" 'howm-view-filter-uniq)
280   (define-key m "!" 'howm-view-summary-shell-command)
281   (define-key m "T" 'howm-list-toggle-title) ;; defined in other file. dirty!
282   ;;     (define-key m howm-reminder-quick-check-key 'howm-reminder-quick-check)
283   ;;     (define-key m ";" 'howm-view-invoke-action-lock)
284   (define-key m "\C-i" 'howm-view-summary-next-section)
285   (define-key m "\M-\C-i" 'howm-view-summary-previous-section)
286   (define-key m [tab] 'howm-view-summary-next-section)
287   (define-key m [(meta tab)] 'howm-view-summary-previous-section)
288   (howm-view-define-common-key m))
289
290 (let ((m howm-view-contents-mode-map))
291   (define-key m "\C-m" 'howm-view-contents-open)
292   (define-key m "\C-j" 'howm-view-contents-open)
293   (howm-view-define-common-key m))
294
295 ;;; summary
296
297 (defun howm-view-summary (&optional name item-list fl-keywords)
298   (let* ((*howm-view-font-lock-keywords* fl-keywords) ;; ok? [2008-07-11]
299          (r (riffle-summary name item-list ':howm
300                            (howm-view-in-background-p))))
301     (if (null r)
302         (message "No match")
303       ;; We want to entry font-lock keywords even when background-p.
304       (when *howm-view-font-lock-keywords*
305         (setq howm-view-font-lock-keywords *howm-view-font-lock-keywords*)))
306     r))
307
308 ;; (defun howm-view-summary (&optional name item-list)
309 ;;   (let ((*howm-view-font-lock-keywords* t))
310 ;;     (riffle-summary name item-list ':howm)))
311
312 (defun howm-view-summary-open (&optional reverse-delete-p)
313   (interactive "P")
314   (when (not (and howm-view-summary-keep-cursor
315                   (get-buffer-window (riffle-contents-buffer))))
316     (riffle-summary-check t))
317   (let* ((p (riffle-persistent-p howm-view-summary-persistent))
318          (persistent (if reverse-delete-p
319                          (not p)
320                        p)))
321     (howm-record-view-window-configuration)
322     (howm-view-summary-open-sub (not persistent))))
323
324 (defun howm-view-summary-open-sub (&optional kill)
325   (interactive "P")
326   (let ((b (riffle-contents-buffer))
327         (looking-at-str (buffer-substring-no-properties (point)
328                                                         (line-end-position))))
329     (riffle-pop-to-buffer b howm-view-summary-window-size)
330     (let ((howm-view-open-hook nil)) ;; Don't execute it in contents-open.
331       (howm-view-contents-open-sub kill))
332     (end-of-line)
333     (or (search-backward looking-at-str (line-beginning-position) t)
334         (beginning-of-line))
335     (run-hooks 'howm-view-open-hook)))
336
337 (defvar howm-view-summary-item-previous-name nil
338   "for internal use")
339 (defun howm-view-summary-item (item)
340   ;; Clean me. This depends on implementation of `riffle-summary-show'
341   ;; severely.
342   (when (eq (point) (point-min))
343     (setq howm-view-summary-item-previous-name ""))
344   (let* ((f (howm-item-name item))
345          (name (if (and howm-view-summary-omit-same-name
346                         (string= f howm-view-summary-item-previous-name))
347                    ""
348                  (progn
349                    (setq howm-view-summary-item-previous-name f)
350                    (howm-view-item-basename item t))))
351          (h (format howm-view-summary-format name)))
352     (concat h (howm-view-item-summary item))))
353
354 (defun howm-view-summary-next-section (&optional n)
355   (interactive "P")
356   (setq n (or n 1))
357   (let ((i (abs n))
358         (step (if (>= n 0) 1 -1)))
359     (while (and (> i 0)
360                 (howm-view-summary-next-section-sub step))
361       (setq i (1- i)))))
362 (defun howm-view-summary-previous-section (&optional n)
363   (interactive "P")
364   (setq n (or n 1))
365   (howm-view-summary-next-section (- n)))
366 (defun howm-view-summary-next-section-sub (step)
367   ;; inefficient. so what?
368   (let* ((f (lambda ()
369               (howm-view-item-filename (riffle-summary-current-item))))
370 ;;               (riffle-controller 'section (riffle-summary-current-item))))
371          (cont-p (lambda ()
372                    (save-excursion
373                      (let ((a (funcall f)))
374                        (forward-line -1)
375                        (string= a (funcall f)))))))
376     (while (and (= (forward-line step) 0)
377                 (funcall cont-p))
378       ;; no body
379       )))
380
381 ;;; contents
382
383 (defun howm-view-contents-open (&optional reverse-delete-p)
384   (interactive "P")
385   (let* ((p (riffle-persistent-p howm-view-contents-persistent))
386          (persistent (if reverse-delete-p
387                          (not p)
388                        p)))
389     (howm-record-view-window-configuration)
390     (howm-view-contents-open-sub (not persistent))))
391
392 (defvar *howm-view-item-privilege* nil) ;; dirty
393
394 (defun howm-view-contents-open-sub (&optional kill)
395   (let* ((item (riffle-contents-current-item))
396          (page (howm-item-page item))
397          (offset (howm-view-item-offset item))
398          (pos (- (point) offset))
399          (viewer (howm-view-external-viewer page)))
400     (when kill
401       (riffle-kill-buffer))
402     (when (howm-view-item-privilege item)
403       (riffle-restore-window-configuration)) ;; force without mode check
404     (setq *howm-view-item-privilege* (howm-view-item-privilege item)) ;; dirty
405     (run-hooks 'howm-view-before-open-hook)
406     (if viewer
407         (howm-view-call-external-viewer viewer page)
408       (howm-view-open-item item
409                            (lambda ()
410                              (when (or (< pos (point-min)) (<= (point-max) pos))
411                                (widen))
412                              (goto-char pos))
413                            t))
414     (run-hooks 'howm-view-open-hook)))
415
416 (defun howm-view-open-item (item &optional position-setter merely)
417   (howm-page-open (howm-item-page item))
418   (howm-view-set-mark-command)
419   (if position-setter
420       (funcall position-setter)
421     (howm-view-set-place (howm-item-place item)))
422   (recenter howm-view-open-recenter)
423   (when (not merely)
424     (howm-view-open-postprocess)))
425 (defun howm-view-open-postprocess ()
426   (run-hooks 'howm-view-open-hook))
427
428 (defvar howm-view-previous-section-page nil "For internal use")
429 (defvar howm-view-previous-section-beg nil "For internal use")
430 (defvar howm-view-previous-section-end nil "For internal use")
431
432 (defun howm-view-contents-item (item)
433   (when (howm-buffer-empty-p)
434     (setq howm-view-previous-section-page ""
435           howm-view-previous-section-beg nil
436           howm-view-previous-section-end nil))
437   (let* ((page (howm-item-page item))
438          (place (howm-view-item-place item))
439          (peq (howm-page= page howm-view-previous-section-page)) ;; dirty!
440          (done-p (if place
441                      (and peq
442                           (<= howm-view-previous-section-beg place)
443                           (<= place howm-view-previous-section-end))
444                    peq)))
445     (if done-p
446         ""
447       (let* ((header (if (null (cdr (howm-view-item-list))) ;; dirty!
448                          ""
449                        (format howm-view-header-format
450                                (howm-page-abbreviate-name page))))
451              (header-length (howm-view-string-point-count header))
452              (viewer (howm-view-external-viewer page)))
453         (concat header
454                 (howm-view-contents-item-sub item page place header viewer
455                                              (+ (point) header-length)))))))
456
457 (defvar howm-view-string-point-count-strict nil)
458 (defun howm-view-string-point-count (str)
459   "Count points of string STR.
460 Namely, it is the difference between start position and end position
461 of STR if STR is inserted to a buffer.
462 It looks to be simply equal to (length STR) on emacs-21.1.1.
463 But I'm not sure for multi-byte characters on other versions of emacsen."
464   (if howm-view-string-point-count-strict
465       (with-temp-buffer
466         (insert str)
467         (- (point) (point-min)))
468     ;; I assume (length (buffer-substring-no-properties START END))
469     ;; is equal to (abs (- START END))). Is it correct?
470     ;; (cf.) snap://Info-mode/elisp#Positions
471     (length str)))
472
473 (defun howm-view-contents-item-sub (item page place header viewer c)
474   (with-temp-buffer
475     (let (b e h)
476       (if viewer
477           (howm-view-contents-indicator viewer page)
478         (howm-page-insert page))
479       (if place
480           (progn
481             (riffle-set-place place)
482             (setq h (point))
483             (let ((r (howm-view-contents-region page)))
484               (setq b (car r)
485                     e (cadr r))))
486         (setq b (point-min)
487               e (point-max)
488               h b))
489       (howm-view-item-set-offset item (- c b))
490       (howm-view-item-set-home item (+ c (- b) h))
491       (setq howm-view-previous-section-page page ;; dirty!
492             howm-view-previous-section-beg (riffle-get-place b)
493             howm-view-previous-section-end (riffle-get-place e))
494       (buffer-substring-no-properties b e))))
495
496 (defvar howm-view-preview-narrow t)
497 (defun howm-view-contents-region (filename)
498   (when filename
499     (howm-page-set-configuration filename))
500   (if (or howm-view-preview-narrow
501           (not (riffle-preview-p)))
502       (howm-view-paragraph-region)
503     (list (point-min) (point-max))))
504
505 (defun howm-view-contents-indicator (viewer fname)
506   (insert (howm-viewer-indicator viewer fname)))
507
508 (defun howm-view-paragraph-region (&optional include-following-blank-p)
509   (let ((b (save-excursion
510              (end-of-line)
511              (re-search-backward howm-view-title-regexp
512                                  nil 'to-limit)
513              (line-beginning-position)))
514         (e (save-excursion
515              (end-of-line)
516              (let ((found (re-search-forward howm-view-title-regexp
517                                              nil 'to-limit)))
518                (if include-following-blank-p
519                    (if found (match-beginning 0) (point-max))
520                  (progn
521                    (if found
522                        (forward-line -1)
523                      (goto-char (point-max)))
524 ;                   (end-of-line)
525                    (while (and (looking-at "^$")
526                                (= (forward-line -1) 0)) ;; successful
527                      nil) ;; dummy
528                    (end-of-line)
529                    (point)))))))
530     (list b e)))
531
532 (defun howm-view-set-mark-command ()
533   (set-mark-command nil)
534   (howm-deactivate-mark))
535
536 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
537 ;;; misc.
538
539 (defun howm-view-file-list (&optional item-list)
540   (howm-cl-remove-duplicates* (mapcar #'howm-view-item-filename
541                                       (or item-list (howm-view-item-list)))
542                               :test #'howm-page=))
543
544 (defun howm-view-mtime (file)
545   (howm-view-time-to-string (howm-page-mtime file)))
546
547 ;; (defun howm-view-xtime (file x)
548 ;;   (let* ((a (file-attributes file))
549 ;;          (n (cdr (assoc x '((a . 4) (m . 5) (c . 6)))))
550 ;;          (ti (nth n a)))
551 ;;     (howm-view-time-to-string ti)))
552
553 (defun howm-view-time-to-string (ti)
554   (format-time-string "%Y%m%d-%H%M%S" ti))
555
556 (defun howm-view-string> (a b)
557   (string< b a))
558
559 (defun howm-view-string<= (a b)
560   (not (string< b a)))
561
562 (defun howm-view-string< (a b)
563   (string< a b))
564
565 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
566 ;;; dir
567
568 (defun howm-view-directory (dir &optional recursive-p)
569   (howm-view-summary "" (howm-folder-items dir recursive-p)))
570
571 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
572 ;;; filter
573
574 (defun howm-view-filter (&optional remove-p)
575   (interactive "P")
576   (let* ((table howm-view-filter-methods)
577          (command (completing-read (if remove-p
578                                        "(Reject) filter by: "
579                                      "filter by: ")
580                                    table nil t)))
581     (call-interactively (cdr (assoc command table)))))
582
583 (defun howm-view-filter-uniq ()
584   (interactive)
585   (howm-view-filter-doit #'howm-filter-items-uniq))
586
587 (defun howm-view-filter-by-name (&optional remove-p regexp)
588   (interactive "P")
589   (howm-view-filter-by-name/summary #'howm-filter-items-by-name
590                                     regexp remove-p))
591
592 (defun howm-view-filter-by-summary (&optional remove-p regexp)
593   (interactive "P")
594   (howm-view-filter-by-name/summary #'howm-filter-items-by-summary
595                                     regexp remove-p))
596
597 (defun howm-view-filter-by-name/summary (filter regexp remove-p)
598   (let* ((r (or regexp (howm-view-filter-read-from-minibuffer "Regexp: "
599                                                               remove-p)))
600          (f `(lambda (item-list rmv-p)
601                (funcall #',filter item-list ,r rmv-p))))
602     (howm-view-filter-doit f remove-p)))
603
604 (defun howm-view-filter-by-date (&optional remove-p)
605   (interactive "P")
606   (howm-view-filter-by-time-range #'howm-filter-items-by-date
607                                   remove-p))
608
609 (defun howm-view-filter-by-reminder (&optional remove-p)
610   (interactive "P")
611   (howm-view-filter-by-time-range #'howm-filter-items-by-reminder remove-p))
612
613 (defun howm-view-filter-by-mtime (&optional remove-p range)
614   (interactive "P")
615   (howm-view-filter-by-time-range #'howm-filter-items-by-mtime remove-p range))
616
617 (defun howm-view-filter-by-time-range (filter &optional remove-p range)
618   (let* ((r (or range (howm-view-ask-time-range remove-p)))
619          (from (car r))
620          (to (cadr r))
621          (f `(lambda (item-list rmv-p)
622                (funcall #',filter item-list ',from ',to rmv-p))))
623     (howm-view-filter-doit f remove-p)))
624
625 (defun howm-view-filter-by-region (beg end)
626   (interactive "r")
627   (let ((r (mapcar #'howm-view-line-number (list beg end))))
628     (howm-view-filter-by-line-range (car r) (cadr r))))
629
630 (defvar howm-view-filter-by-around-default 10)
631 (defun howm-view-filter-by-around (&optional distance)
632   (interactive "P")
633   (let* ((d (or distance howm-view-filter-by-around-default))
634          (c (howm-view-line-number)))
635     (howm-view-filter-by-line-range (- c d) (+ c d))))
636
637 (defun howm-view-filter-by-line-range (beg end)
638   (let ((f `(lambda (item-list remove-p)
639               (when remove-p
640                 (error "Not supported."))
641               ;; beg and end are counted as 1,2,3,...
642               (cl-subseq item-list
643                               (max (1- ,beg) 0)
644                               ;; end is included.
645                               (min ,end (length item-list))))))
646     (howm-view-filter-doit f)))
647
648 (defun howm-view-filter-by-contents (&optional remove-p regexp)
649   (interactive "P")
650   (let ((r (or regexp (howm-view-filter-read-from-minibuffer
651                        "Search in result (grep): "
652                        remove-p))))
653     (if remove-p
654         (howm-view-remove-by-contents r)
655       (howm-view-search-in-result r))))
656
657 (howm-if-ver1dot3 nil
658   (defcustom howm-view-search-in-result-correctly t
659     "*Non nil if search-in-result should be aware of paragraph."
660     :type 'boolean
661     :group 'howm-search))
662
663 (defun howm-view-search-in-result (regexp)
664 ;;   (interactive "sSearch in result (grep): ")
665   (let* ((orig (howm-view-name))
666          (name (if (string= orig "")
667                    regexp
668                  (format "%s&%s" orig regexp)))
669          (orig-item-list (howm-view-item-list))
670          (folder (howm-make-folder-from-items orig-item-list)))
671     (howm-write-history regexp)
672     (howm-view-search-folder regexp folder name)
673     (when howm-view-search-in-result-correctly
674       (howm-view-summary-rebuild (howm-item-list-filter (howm-view-item-list)
675                                                         orig-item-list)))))
676
677 (defun howm-view-remove-by-contents (regexp)
678 ;;   (interactive "s(Reject) Search in result (grep): ")
679   (let ((howm-v-r-b-c-regexp regexp))
680     (howm-view-sort/filter-doit
681      (lambda (item-list switch)
682        (howm-filter-items-by-contents item-list howm-v-r-b-c-regexp t)))))
683
684 (defun howm-view-sort/filter-doit (proc &optional switch)
685   (let ((kw font-lock-keywords))
686     (prog1
687         ;; return item-list for
688         ;; https://howm.osdn.jp/cgi-bin/hiki/hiki.cgi?HidePrivateReminder
689         (howm-view-summary-rebuild (funcall proc (howm-view-item-list) switch))
690       (setq font-lock-keywords kw))))
691
692 (defalias 'howm-view-filter-doit 'howm-view-sort/filter-doit)
693
694 ;; For backward compatibility with howmoney. Don't use this.
695 (defun howm-view-filter-general (pred)
696   (howm-view-filter-doit (lambda (item-list dummy)
697                            (cl-remove-if-not pred item-list))))
698 ;; (defun howm-view-filter-general (pred &optional remove-p with-index)
699 ;;   (let* ((item-list (howm-view-item-list))
700 ;;          (s (if with-index
701 ;;                 (howm-map-with-index #'list item-list)
702 ;;               item-list))
703 ;;          (r (if remove-p
704 ;;                 (cl-remove-if pred s)
705 ;;               (cl-remove-if-not pred s)))
706 ;;          (filtered (if with-index
707 ;;                        (mapcar #'car r)
708 ;;                      r)))
709 ;;     (howm-view-summary-rebuild filtered)))
710
711 (defmacro howm-filter-items (pred lis &optional remove-p)
712   `(if ,remove-p
713        (cl-remove-if ,pred ,lis)
714      (cl-remove-if-not ,pred ,lis)))
715
716 (defun howm-filter-items-uniq (item-list &optional remove-p)
717   (when remove-p
718     (error "Not supported."))
719   (let* ((howm-view-filter-uniq-prev (if howm-view-search-in-result-correctly
720                                          (cons "" nil)
721                                        ""))
722          (pred (if howm-view-search-in-result-correctly
723                    (lambda (item)
724                      (let ((page (howm-item-page item))
725                            (place (howm-item-place item))
726                            (range (howm-item-range item))
727                            (p-page  (car howm-view-filter-uniq-prev))
728                            (p-range (cdr howm-view-filter-uniq-prev)))
729                        (prog1
730                            (not (and (howm-page= page p-page)
731                                      (and place p-range
732                                           (<= (car p-range) place)
733                                           (<= place (cadr p-range)))))
734                          (setq howm-view-filter-uniq-prev (cons page range)))))
735                  ;; old code
736                  (lambda (item)
737                    (let ((f (howm-view-item-filename item)))
738                      (prog1
739                          (not (howm-page= f howm-view-filter-uniq-prev))
740                        (setq howm-view-filter-uniq-prev f)))))))
741     (cl-remove-if-not pred item-list)))
742
743 (defun howm-filter-items-by-name (item-list regexp &optional remove-p)
744   (howm-filter-items-by-name/summary #'howm-view-item-basename
745                                      item-list regexp remove-p))
746
747 (defun howm-filter-items-by-summary (item-list regexp &optional remove-p)
748   (howm-filter-items-by-name/summary #'howm-view-item-summary
749                                      item-list regexp remove-p))
750
751 (defun howm-filter-items-by-name/summary (accessor item-list regexp remove-p)
752   (howm-filter-items (lambda (item)
753                        (string-match regexp (funcall accessor item)))
754                      item-list remove-p))
755
756 (defun howm-filter-items-by-date (item-list from to &optional remove-p)
757   (let* ((form (howm-view-file-name-format))
758          (fts (mapcar (lambda (x)
759                         (file-name-nondirectory (format-time-string form x)))
760                       (list from to)))
761          (fs (car fts))
762          (ts (cadr fts)))
763     (howm-filter-items (lambda (item)
764                          (let ((cs (howm-view-item-basename item)))
765                            (and (howm-view-string<= fs cs)
766                                 (howm-view-string< cs ts))))
767                        item-list remove-p)))
768
769 (defun howm-filter-items-by-reminder (item-list from to &optional remove-p)
770   (let* ((from-str (format-time-string howm-date-format from))
771          (to-str (format-time-string howm-date-format to))
772          (reg (howm-reminder-regexp howm-reminder-types)))
773     (howm-filter-items
774      (lambda (item)
775        (let ((s (howm-view-item-summary item)))
776          (and (string-match reg s)
777               (let* ((x (match-string-no-properties 0 s)) ;; [2004-02-07]@
778                      (d (and (string-match howm-date-regexp x)
779                              (match-string-no-properties 0 x)))) ;; [2004-02-07]
780                 (and (howm-view-string<= from-str d)
781                      (howm-view-string< d to-str))))))
782      item-list remove-p)))
783
784 (defun howm-filter-items-by-mtime (item-list from to &optional remove-p)
785   (let ((fs (howm-view-time-to-string from))
786         (ts (howm-view-time-to-string to)))
787     (howm-filter-items
788      (lambda (item)
789        (let ((cs (howm-view-mtime (howm-view-item-filename item))))
790          (and (howm-view-string<= fs cs)
791               (howm-view-string< cs ts))))
792      item-list remove-p)))
793
794 (defun howm-filter-items-by-contents (item-list regexp &optional remove-p)
795   (let* ((match (howm-view-search-folder-items-fi regexp item-list)))
796     (if howm-view-search-in-result-correctly
797         (howm-item-list-filter item-list match remove-p)
798       ;; old behavior
799       (let ((match-names (howm-cl-remove-duplicates*
800                           (mapcar #'howm-item-name match))))
801         (howm-filter-items (lambda (item)
802                              (member (howm-item-name item) match-names))
803                            item-list remove-p)))))
804
805 (defun howm-view-file-name-format ()
806   howm-file-name-format) ;; defined in howm-common.el
807
808 (defun howm-view-ask-time-range (&optional remove-p)
809   (let* ((now (current-time))
810          (from (howm-view-ask-time "From" now t remove-p))
811          (to (howm-view-ask-time "To" from nil remove-p)))
812     (list from to)))
813
814 (defvar howm-view-min-year 1950)
815 (defvar howm-view-max-year 2030)
816 (defun howm-view-ask-time (prompt default &optional from-p remove-p)
817   (let* ((z (decode-time default))
818          (yd (nth 5 z))
819          (md (nth 4 z))
820          (dd (nth 3 z)))
821     (let (y0 m0 d0 hour0 min0 sec0)
822       (if from-p
823           (setq y0 howm-view-min-year m0 1 d0 1
824                 hour0 0 min0 0 sec0 0)
825         (setq y0 howm-view-max-year m0 12 d0 'last-day-of-month
826               hour0 24 min0 0 sec0 0))
827       (let ((y (howm-ask-time-sub prompt "year" yd remove-p)))
828         (if (null y)
829             (howm-view-encode-time sec0 min0 hour0 d0 m0 y0)
830           (let ((m (howm-ask-time-sub prompt "month" md remove-p)))
831             (if (null m)
832                 (howm-view-encode-time sec0 min0 hour0 d0 m0 y)
833               (let ((d (or (howm-ask-time-sub prompt "date" dd remove-p) d0)))
834                 (howm-view-encode-time sec0 min0 hour0 d m y)))))))))
835
836 (defun howm-ask-time-sub (prompt ymd default remove-p)
837   (let* ((message (format "%s %s (* = no limit) [%d]: " prompt ymd  default))
838          (raw (howm-view-filter-read-from-minibuffer message remove-p))
839          (n (if (string= raw "")
840                 default
841               (string-to-number raw))))
842     (if (= n 0)
843         nil
844       n)))
845
846 (defun howm-view-encode-time (sec min hour d m y)
847   (when (eq d 'last-day-of-month)
848     (setq m (+ m 1))
849     (setq d -1))
850   (encode-time sec min hour d m y))
851
852 (defun howm-view-filter-read-from-minibuffer (message &optional remove-p)
853   (read-from-minibuffer (if remove-p
854                             (concat "(Reject) " message)
855                           message)))
856
857 (defun howm-view-summary-rebuild (item-list &optional fl-keywords)
858   (howm-view-summary (howm-view-name) item-list fl-keywords))
859
860 (let* ((h (regexp-quote howm-view-title-header))
861        (t1 (format "Skip \"%s \"" howm-view-title-header))
862        (r1 (format "^\\(%s\\)? *$" h))
863        (t2 (format "Skip \"%s \" and \"[xxxx-xx-xx xx:xx]\""
864                    howm-view-title-header))
865        (r2 (format "\\(%s\\)\\|\\(^\\[[-: 0-9]+\\]\\)" r1)))
866   (howm-if-ver1dot3 nil
867     (defcustom howm-view-title-skip-regexp r2
868       "*Regular expression for lines which should not be titles.
869 If the original title matches this regexp, the first non-matched line
870 is shown as title instead.
871 Nil disables this feature.
872
873 This feature does not work when `howm-view-search-in-result-correctly' is nil."
874       :type `(radio (const :tag "Off" nil)
875                     (const :tag ,t1 ,r1)
876                     (const :tag ,t2 ,r2)
877                     regexp)
878       :group 'howm-title
879       :group 'howm-efficiency)))
880
881 (defcustom howm-view-list-title-type 1
882   "*Type of showing title in summary buffer.
883 Value 1 means \"show title instead of summary\".
884 Value 2 means \"show title before summary\".
885 You may want to set `howm-view-summary-format' to be \"\" if you never need
886 to see file names."
887   :type '(radio (const :tag "title instead of summary"
888                        1)
889                 (const :tag "title before summary"
890                        2))
891   :group 'howm-experimental)
892
893 (defun howm-view-list-title (title-regexp)
894   (howm-view-summary-rebuild (howm-entitle-items
895                               title-regexp (howm-view-item-list))))
896
897 (defun howm-entitle-items (title-regexp item-list)
898   (if (= howm-view-list-title-type 1)
899       (howm-entitle-items-style1 title-regexp item-list)
900     (howm-entitle-items-style2 title-regexp item-list)))
901
902 (defun howm-entitle-items-style1 (title-regexp item-list)
903   "Put title instead of summary."
904   (let ((items (howm-view-search-folder-items-fi title-regexp item-list)))
905     (if howm-view-search-in-result-correctly
906         (let* ((r (howm-item-list-filter items item-list 'with-rest))
907                (hit-items (car r))
908                (nohit-items (cdr r))
909                ;; should I use (howm-classify #'howm-item-place nohit-items) ?
910                (noplace-nohit-items
911                 (cl-remove-if #'howm-item-place nohit-items))
912                (rest-items
913                 (howm-item-list-filter (cl-remove-if-not #'howm-item-place
914                                                               nohit-items)
915                                        items t))
916                (all-items (append hit-items noplace-nohit-items rest-items)))
917           (when howm-view-title-skip-regexp
918             (mapc #'howm-view-change-title all-items))
919           all-items)
920       (let* ((pages (howm-cl-remove-duplicates* (mapcar #'howm-item-page
921                                                         item-list)))
922              (hit-pages (mapcar #'howm-item-page items))
923              (nohit-pages (cl-remove-if
924                            (lambda (p) (cl-member p hit-pages
925                                                         :test #'howm-page=))
926                            pages))
927              (nohit-items (mapcar #'howm-make-item nohit-pages))
928              (all-items (if (null nohit-items)
929                             items
930                           (append items nohit-items))))
931         all-items))))
932
933 (defvar howm-entitle-items-style2-max-length 20)
934 (defvar howm-entitle-items-style2-format "%-13s | %s") ;; for title and summary
935 (defvar howm-entitle-items-style2-title-line nil) ;; independent title line?
936 (defun howm-entitle-items-style2 (title-regexp item-list)
937   "Put title before summary."
938   ;; fix me: howm-item-place is not set for howm-list-all
939   (let ((last-title ""))
940     (cl-mapcan
941      (lambda (item)
942        (let ((orig (howm-item-summary item))
943              (titles (howm-item-titles item)))
944          (cl-mapcan
945           (lambda (s)
946             (if (string= s last-title)
947                 (setq s "")
948               (setq last-title s))
949             (when (> (length s) howm-entitle-items-style2-max-length)
950               (setq s (substring s 0 howm-entitle-items-style2-max-length)))
951             (mapcar (lambda (x)
952                       (let ((i (howm-item-dup item)))
953                         (howm-item-set-summary i x)
954                         i))
955                     (if (and howm-entitle-items-style2-title-line
956                              (not (string= s "")))
957                         (list (format howm-entitle-items-style2-format
958                                       s "")
959                               (format howm-entitle-items-style2-format
960                                       "" orig))
961                       (list (format howm-entitle-items-style2-format
962                                     s orig)))))
963           (or titles (list "")))))
964      item-list)))
965
966 ;;; detect items in same paragraph (= entry = memo. sorry for inconsistent terminology)
967
968 (defun howm-item-with-temp-buffer (item proc)
969   (with-temp-buffer
970     (howm-page-insert (howm-item-page item))
971     (let* ((p (howm-item-place item))
972            (r (if (null p)
973                   (list (point-min) (point-max))
974                 (progn
975                   (riffle-set-place p)
976                   (howm-view-paragraph-region)))))
977       (narrow-to-region (car r) (cadr r))
978       (funcall proc item))))
979
980 (defun howm-item-titles (item)
981   "List of titles of ITEM.
982 When place (see `howm-item-place') is specified, ITEM has at most one title.
983 Otherwise, ITEM can have two or more titles."
984   (howm-item-with-temp-buffer
985    item
986    (lambda (i)
987      (let ((titles nil))
988        (goto-char (point-min))
989        (while (re-search-forward (howm-list-title-regexp) nil t)
990          (setq titles
991                (cons (buffer-substring-no-properties (match-beginning 0)
992                                                      (line-end-position))
993                      titles)))
994        (mapcar (lambda (x)
995                  (if (string-match howm-view-title-regexp x)
996                      (match-string-no-properties howm-view-title-regexp-pos x)
997                    x))
998                (reverse titles))))))
999
1000 (defun howm-item-range (item)
1001   "List of beginning-place and end-place of paragraph to which ITEM belongs."
1002   (howm-item-with-temp-buffer
1003    item
1004    (lambda (i)
1005      (let ((r (list (point-min) (point-max))))
1006        (widen)
1007        (list (progn
1008                (goto-char (car r))
1009                (riffle-get-place))
1010              (progn
1011                (goto-char (cadr r))
1012                (riffle-get-place)))))))
1013 ;;   (with-temp-buffer
1014 ;;     (howm-page-insert (howm-item-page item))
1015 ;;     (let* ((p (howm-item-place item))
1016 ;;            (r (if (null p)
1017 ;;                   (list (point-min) (point-max))
1018 ;;                 (progn
1019 ;;                   (riffle-set-place p)
1020 ;;                   (howm-view-paragraph-region)))))
1021 ;;       (list (progn
1022 ;;               (goto-char (car r))
1023 ;;               (riffle-get-place))
1024 ;;             (progn
1025 ;;               (goto-char (cadr r))
1026 ;;               (riffle-get-place))))))
1027
1028 (defun howm-item-list-rangeset (item-list)
1029   "Make assoc list of page to rangeset.
1030 ITEM-LIST is list of items.
1031 Return value is assoc list; each element of it is a cons pair of page
1032 and rangeset which indicates ranges of places of paragraphs to which items
1033 in ITEM-LIST belongs."
1034   (let ((alist nil))  ;; key = page, value = rangeset of place
1035     (cl-labels ((add-to-alist (page rs)
1036                            (setq alist (cons (cons page rs) alist))))
1037       (mapc (lambda (item)
1038               (let* ((page (howm-item-page item))
1039                      (place (howm-item-place item))
1040                      (rs (cdr (assoc page alist))))
1041                 (cond ((null place)
1042                        (add-to-alist page (howm-make-rangeset)))
1043                       ((null rs)
1044                        (add-to-alist page (howm-make-rangeset
1045                                            (howm-item-range item))))
1046                       ((howm-rangeset-belong-p place rs)
1047                        nil) ;; do nothing
1048                       (t
1049                        (howm-rangeset-add! rs (howm-item-range item))))))
1050             item-list)
1051       alist)))
1052
1053 (defun howm-item-list-filter (item-list reference-item-list
1054                                         &optional remove-match)
1055   "Select items in ITEM-LIST according to REFERENCE-ITEM-LIST.
1056 When REMOVE-MATCH is nil, return value is list of items i in ITEM-LIST
1057 which satisfy the condition \"there exists i' in REFERENCE-ITEM-LIST
1058 such that i and i' belong to same paragraph\" (case 1).
1059 When REMOVE-MATCH is non-nil and not the symbol 'with-rest',
1060 return value is complement of the above list;
1061 list of items in ITEM-LIST which do not satisfy the above condition (case 2).
1062 When REMOVE-MATCH is the symbol 'with-rest',
1063 return value is (A . B), where A is the return value of case 1 and
1064 B is items in REFERENCE-ITEM-LIST that do not match in case 1."
1065   ;; 
1066   ;; split no-place items:
1067   ;; Though implementation 1 calls grep many times,
1068   ;; implementation 2 is slower in construction of folder from items.
1069   ;; [2012-12-28]
1070   ;; 
1071   ;; implementation 1 (call grep many times)
1072   (setq item-list
1073         (cl-mapcan (lambda (item)
1074                           (if (howm-item-place item)
1075                               (list item)
1076                             (or (howm-view-search-folder-items-fi
1077                                  (howm-view-title-regexp-grep) (list item))
1078                                 (list item))))
1079                         item-list))
1080   ;; 
1081   ;; ;; implementation 2 (making items-folder is slow)
1082   ;; (let* ((place-items (cl-remove-if-not #'howm-item-place item-list))
1083   ;;        (no-place-items (cl-remove-if #'howm-item-place item-list))
1084   ;;        (split-items (howm-view-search-folder-items-fi
1085   ;;                      (howm-view-title-regexp-grep) no-place-items))
1086   ;;        ;;; !!!!!!!!! use CL !!!!!!!!!!!!!!!!!!!!!!!!!!!!
1087   ;;        (no-title-items (set-difference no-place-items split-items
1088   ;;                                        :key #'howm-item-page)))
1089   ;;   (setq item-list (append place-items split-items no-title-items)))
1090   ;;
1091   (let* ((alist (howm-item-list-rangeset reference-item-list))
1092          (matcher (lambda (item)
1093                     (let* ((page (howm-item-page item))
1094                            (place (howm-item-place item))
1095                            (rs (cdr (assoc page alist))))
1096                       (cond ((null rs) nil)
1097                             ((howm-rangeset-belong-p place rs) rs)
1098                             (t nil))))))
1099     (cond ((eq remove-match 'with-rest)
1100            (let ((match (cl-remove-if-not
1101                          (lambda (item)
1102                            (let ((rs (funcall matcher item)))
1103                              (and rs (howm-rangeset-hit! rs))))
1104                          item-list)))
1105              (cons match
1106                    (cl-mapcan
1107                     (lambda (a) (and (not (howm-rangeset-hit-p (cdr a)))
1108                                      (list (howm-make-item (car a)))))
1109                     alist))))
1110           (remove-match (cl-remove-if matcher item-list))
1111           (t (cl-remove-if-not matcher item-list)))))
1112
1113 ;;; rangeset
1114 ;;; ex.
1115 ;;; (*rangeset* (1 . 4) (5 . 6) (8 . 14))
1116 ;;; (*rangeset*) ==> "almighty"
1117 ;;; (*rangeset-hit* (1 . 4) (5 . 6) (8 . 14)) ==> "hit" is recorded
1118
1119 (defun howm-make-rangeset (&optional beg-end)
1120   (if (null beg-end)
1121       (cons '*rangeset* nil)
1122     (let ((rs (howm-make-rangeset)))
1123       (howm-rangeset-add! rs beg-end))))
1124
1125 (defun howm-rangeset-belong-p (point rs)
1126   (or (null (cdr rs))
1127       (cl-member-if (lambda (pair)
1128                            (and (<= (car pair) point) (<= point (cdr pair))))
1129                          (cdr rs))))
1130
1131 (defun howm-rangeset-add! (rs beg-end)
1132   ;; "almighty" is ignored here. sorry for confusion...
1133   ;; c = cursor (pointing its cdr)
1134   ;; p = pair
1135   (let ((c rs)
1136         (beg (car beg-end))
1137         (end (cadr beg-end)))
1138     (while (and (cdr c) beg)
1139       (let ((p (cadr c)))
1140         (cond ((< end (car p)) ;; insert [beg, end] here
1141                (rplacd c (cons (cons beg end) (cdr c)))
1142                (setq beg nil))
1143               ((< (cdr p) beg) ;; skip this
1144                (setq c (cdr c)))
1145               (t ;; merge into [beg, end]
1146                (setq beg (min beg (car p))
1147                      end (max end (cdr p)))
1148                (rplacd c (cddr c))))))
1149     (when beg
1150       (rplacd c (list (cons beg end)))))
1151   rs)
1152
1153 (defvar howm-rangeset-hit-indicator '*rangeset-hit*)
1154
1155 (defun howm-rangeset-hit! (rs)
1156   (setcar rs howm-rangeset-hit-indicator))
1157
1158 (defun howm-rangeset-hit-p (rs)
1159   (eq (car rs) howm-rangeset-hit-indicator))
1160
1161 ;; check
1162
1163 (let ((tests '(
1164                (()
1165                 ())
1166                (((3 . 5))
1167                 ((3 . 5)))
1168                (((3 . 5) (0 . 1))
1169                 ((0 . 1) (3 . 5)))
1170                (((3 . 5) (6 . 8))
1171                 ((3 . 5) (6 . 8)))
1172                (((3 . 5) (1 . 4))
1173                 ((1 . 5)))
1174                (((3 . 5) (4 . 7))
1175                 ((3 . 7)))
1176                (((3 . 5) (1 . 9))
1177                 ((1 . 9)))
1178                (((3 . 1) (4 . 1) (5 . 9))
1179                 ((1 . 4) (5 . 9)))
1180                (((3 . 1) (4 . 1) (5 . 9) (2 . 6) (5 . 3))
1181                 ((1 . 9)))
1182                ))
1183        ;; inhibit 'reference to free variable' warning in byte-compilation
1184       (check nil))
1185   (cl-labels ((check (ans result)
1186                   (cond ((null ans) (null result))
1187                         ((not (equal (car ans) (car result))) nil)
1188                         (t (funcall check (cdr ans) (cdr result))))))
1189     (mapc (lambda (z)
1190             (apply (lambda (prob ans)
1191                      (let* ((rs (howm-make-rangeset)))
1192                        (mapc (lambda (pair)
1193                                (let ((a (car pair))
1194                                      (b (cdr pair)))
1195                                  (howm-rangeset-add! rs
1196                                                      (list (min a b)
1197                                                            (max a b)))))
1198                              prob)
1199                        (when (not (equal (cdr rs) ans))
1200                          (error "howm-rangeset-add: %s ==> %s" prob rs))))
1201                    z))
1202           tests)))
1203
1204 (let ((rs '(*rangeset* (1 . 4) (5 . 6) (8 . 14))))
1205   (if (and (howm-rangeset-belong-p 1 rs)
1206            (howm-rangeset-belong-p 3 rs)
1207            (howm-rangeset-belong-p 4 rs)
1208            (howm-rangeset-belong-p 5 rs)
1209            (not (howm-rangeset-belong-p 0 rs))
1210            (not (howm-rangeset-belong-p 4.5 rs))
1211            (not (howm-rangeset-belong-p 7 rs))
1212            (not (howm-rangeset-belong-p 15 rs)))
1213       t
1214     (error "howm-rangeset-belong-p: wrong result")))
1215
1216 (defun howm-view-change-title (item)
1217   (when (string-match howm-view-title-skip-regexp (howm-item-summary item))
1218     (let ((title-line (with-temp-buffer
1219                         (howm-page-insert (howm-item-page item))
1220                         (howm-view-set-place (or (howm-item-place item)
1221                                                  (howm-view-get-place
1222                                                   (point-min))))
1223                         (howm-view-get-title-line))))
1224       (howm-item-set-summary item title-line))))
1225
1226 (defun howm-view-get-title-line ()
1227   (while (and (looking-at howm-view-title-skip-regexp)
1228               (= (forward-line 1) 0))
1229     ;; do nothine
1230     )
1231   (buffer-substring-no-properties (line-beginning-position)
1232                                   (line-end-position)))
1233
1234 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1235 ;;; search
1236
1237 (defun howm-view-search (str file-list &optional
1238                              name summarizer fixed-p hilit-keywords)
1239   "This function is not used in howm any more."
1240   (howm-view-search-folder str (howm-make-folder:files file-list)
1241                            name summarizer fixed-p hilit-keywords))
1242
1243 (defun howm-view-search-items (str file-list &optional summarizer fixed-p)
1244   (howm-view-search-folder-items str (howm-make-folder:files file-list)
1245                                  summarizer fixed-p))
1246
1247 (defun howm-view-search-folder (&rest args)
1248   (howm-view-search-folder-doit (apply #'howm-view-search-folder-internal
1249                                        args)))
1250
1251 (defun howm-view-search-folder-internal (str folder
1252                                              &optional name summarizer
1253                                              fixed-p hilit-keywords)
1254   ;; clean me. str-orig can be string or list of strings.
1255   (let* ((str-orig str)
1256          (str-list (if (listp str-orig) str-orig (list str-orig)))
1257          (str-principal (if (listp str-orig) (car str-orig) str-orig)))
1258     ;; rename str
1259     (setq str str-principal)
1260     (setq name (or name str))
1261     (when howm-view-update-search-ring
1262       (isearch-update-ring str (not fixed-p)))
1263     (let* ((items (howm-view-search-folder-items str-orig
1264                                                  folder summarizer fixed-p))
1265            (kw (or hilit-keywords
1266                    (let ((r (if fixed-p
1267                                 (regexp-opt str-list)
1268                               (mapconcat (lambda (x) (concat "\\(" x "\\)"))
1269                                          str-list
1270                                          "\\|"))))
1271                      `((,r . howm-view-hilit-face))))))
1272       (let* ((f (expand-file-name str)))
1273         (when (file-exists-p f)
1274           (let ((fi (howm-view-make-item f)))
1275             (howm-view-item-set-privilege fi t)
1276             (setq items (cons fi items)))))
1277       (list kw name items))))
1278
1279 (defun howm-view-search-folder-doit (p)
1280   (howm-view-summary (cadr p) (cl-caddr p) (car p)))
1281
1282 (defun howm-view-search-folder-items (str folder &optional summarizer fixed-p)
1283   (let ((found (howm-folder-grep folder str fixed-p))
1284         (summarizer (or summarizer
1285                         (lambda (file place content)
1286                           (string-match "^ *\\(.*\\)" content)
1287                           (match-string-no-properties 1 content)))))
1288     (mapc (lambda (i)
1289             (let ((file (howm-page-name (howm-item-page i)))
1290                   (place (howm-item-place i))
1291                   (content (howm-item-summary i)))
1292               (howm-item-set-summary i (funcall summarizer
1293                                                 file place content))))
1294           found)
1295     found))
1296
1297 ;; sorry for confusing functions...
1298
1299 (defun howm-view-search-folder-items-fi (regexp item-list &rest args)
1300   (apply #'howm-view-search-folder-items
1301          regexp (howm-make-folder-from-items item-list) args))
1302
1303 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1304 ;;; sort
1305
1306 (defun howm-view-sort ()
1307   (interactive)
1308   (let* ((table howm-view-sort-methods)
1309          (command (completing-read "sort by: " table nil t)))
1310     (call-interactively (cdr (assoc command table)))))
1311
1312 (defmacro howm-view-defun-sort-by (name)
1313   "Define an interactive command howm-view-sort-by-NAME,
1314 which simply calls howm-sort-items-by-NAME."
1315   (let ((command (howm-get-symbol nil "howm-view-sort-by-" name))
1316         (internal (howm-get-symbol nil "howm-sort-items-by-" name)))
1317     `(defun ,command (&optional reverse-p)
1318        (interactive "P")
1319        (howm-view-sort-doit #',internal reverse-p))))
1320 (howm-view-defun-sort-by "random")
1321 (howm-view-defun-sort-by "name")
1322 (howm-view-defun-sort-by "numerical-name")
1323 (howm-view-defun-sort-by "date")
1324 (howm-view-defun-sort-by "reverse-date")
1325 (howm-view-defun-sort-by "summary")
1326 (howm-view-defun-sort-by "reminder")
1327 (howm-view-defun-sort-by "mtime")
1328 (howm-view-defun-sort-by "reverse")
1329
1330 (defalias 'howm-view-sort-reverse 'howm-view-sort-by-reverse)
1331
1332 (defalias 'howm-view-sort-doit 'howm-view-sort/filter-doit)
1333
1334 (defmacro howm-sort-items (evaluator comparer item-list
1335                                              &optional reverse-p)
1336   `(let* ((howm-view-s-i-comparer ,comparer)
1337           (cmp (if reverse-p
1338                    (lambda (a b) (funcall howm-view-s-i-comparer b a))
1339                  howm-view-s-i-comparer)))
1340      (howm-sort ,evaluator cmp item-list)))
1341
1342 ;; ;; generate the below aliases for howm-test080714
1343 ;; (let ((methods '("random" "name" "numerical-name" "date" "reverse-date"
1344 ;;                  "summary" "reminder" "mtime" "reverse")))
1345 ;;   (mapcar (lambda (m)
1346 ;;             (let* ((command
1347 ;;                     (howm-get-symbol nil "howm-view-sort-by-" m))
1348 ;;                    (internal
1349 ;;                     (howm-get-symbol nil "howm-sort-items-by-" m))
1350 ;;                    (obsolete
1351 ;;                     (howm-get-symbol nil command "-internal")))
1352 ;;               `(defalias ',obsolete ',internal)))
1353 ;;           methods))
1354
1355 ;; for backward compatibility with howm-test080714 only
1356 (defalias 'howm-view-sort-by-random-internal 'howm-sort-items-by-random)
1357 (defalias 'howm-view-sort-by-name-internal 'howm-sort-items-by-name)
1358 (defalias 'howm-view-sort-by-numerical-name-internal
1359   'howm-sort-items-by-numerical-name)
1360 (defalias 'howm-view-sort-by-date-internal 'howm-sort-items-by-date)
1361 (defalias 'howm-view-sort-by-reverse-date-internal
1362   'howm-sort-items-by-reverse-date)
1363 (defalias 'howm-view-sort-by-summary-internal 'howm-sort-items-by-summary)
1364 (defalias 'howm-view-sort-by-reminder-internal 'howm-sort-items-by-reminder)
1365 (defalias 'howm-view-sort-by-mtime-internal 'howm-sort-items-by-mtime)
1366 (defalias 'howm-view-sort-by-reverse-internal 'howm-sort-items-by-reverse)
1367
1368 (defun howm-sort-items-by-random (item-list &optional reverse-p)
1369   (howm-sort-items #'(lambda (dummy) (random)) #'< item-list reverse-p))
1370
1371 (defun howm-sort-items-by-name (item-list &optional reverse-p)
1372   (howm-sort-items #'howm-view-item-basename #'string< reverse-p))
1373
1374 (defun howm-sort-items-by-numerical-name (item-list &optional reverse-p)
1375   (howm-sort-items (lambda (i)
1376                              (let ((b (howm-view-item-basename i)))
1377                                (if (string-match "^[0-9]+$" b)
1378                                    (string-to-number b)
1379                                  howm-infinity)))
1380                            #'< reverse-p))
1381
1382 (defvar howm-view-sort-by-date-ignore-regexp "^[a-zA-Z]")
1383 (defun howm-sort-items-by-date (item-list &optional reverse-p)
1384   (let ((sorted (howm-sort-items #'howm-view-item-basename #'string<
1385                                          item-list reverse-p)))
1386     (cdr (howm-view-lift-internal #'howm-view-item-basename
1387                                   sorted
1388                                   howm-view-sort-by-date-ignore-regexp
1389                                   t))))
1390
1391 (defun howm-sort-items-by-reverse-date (item-list &optional reverse-p)
1392   (howm-sort-items-by-date item-list (not reverse-p)))
1393
1394 (defun howm-sort-items-by-summary (item-list &optional reverse-p)
1395   (howm-sort-items #'howm-view-item-summary #'string<
1396                            item-list reverse-p))
1397
1398 (defun howm-sort-items-by-reminder (item-list &optional reverse-p)
1399   (let* ((howm-view-s-b-r-i-regexp (howm-reminder-regexp howm-reminder-types))
1400          (howm-view-s-b-r-i-max (format-time-string
1401                                  howm-reminder-today-format
1402                                  (encode-time 59 59 23 31 12
1403                                               howm-view-max-year)))
1404          (evaluator (lambda (item)
1405                       (let ((s (howm-view-item-summary item)))
1406                         (if (string-match howm-view-s-b-r-i-regexp s)
1407                             (match-string-no-properties 0 s)
1408                           howm-view-s-b-r-i-max)))))
1409     (howm-sort-items evaluator #'string< item-list reverse-p)))
1410
1411 (defun howm-sort-items-by-mtime (item-list &optional reverse-p)
1412   (howm-sort-items (lambda (item)
1413                      (howm-view-mtime (howm-view-item-filename item)))
1414                    #'howm-view-string>
1415                    item-list reverse-p))
1416
1417 (defun howm-sort-items-by-reverse (item-list &optional dummy)
1418   (reverse item-list))
1419
1420 ;;; lift (move matched items to the top)
1421
1422 (defun howm-view-lift-by-name (&optional reverse-p regexp path-p)
1423   (interactive "P")
1424   (howm-view-lift-doit (if path-p
1425                            #'howm-view-lift-by-path-internal
1426                          #'howm-view-lift-by-name-internal)
1427                        reverse-p regexp))
1428
1429 (defun howm-view-lift-by-summary (&optional reverse-p regexp)
1430   (interactive "P")
1431   (howm-view-lift-doit #'howm-view-lift-by-summary-internal
1432                        reverse-p regexp))
1433
1434 (defun howm-view-lift-by-summary-substring (&optional reverse-p regexp
1435                                                       regexp-pos)
1436   (interactive "P")
1437   (howm-view-lift-doit #'howm-view-lift-by-summary-substring-internal
1438                        reverse-p regexp regexp-pos))
1439
1440 (defun howm-view-lift-doit (sorter &optional reverse-p regexp
1441                                             regexp-pos)
1442   (let* ((howm-view-s-b-m-d-regexp (or regexp
1443                                        (read-from-minibuffer "Regexp: ")))
1444          (howm-view-s-b-m-d-regexp-pos regexp-pos)
1445          (howm-view-s-b-m-d-sorter sorter)
1446          (howm-view-s-b-m-d-matched nil))
1447     (howm-view-sort-doit (lambda (item-list rvs-p)
1448                            (let ((p (apply howm-view-s-b-m-d-sorter
1449                                            item-list
1450                                            howm-view-s-b-m-d-regexp
1451                                            rvs-p
1452                                            howm-view-s-b-m-d-regexp-pos)))
1453                              (setq howm-view-s-b-m-d-matched (car p))
1454                              (cdr p)))
1455                          reverse-p)
1456     howm-view-s-b-m-d-matched))
1457
1458 (defun howm-view-lift-internal (picker item-list regexp
1459                                        &optional reverse-p regexp-pos)
1460   "Sort items and return (matched . sorted-list).
1461 matched can be nil, single, or multi."
1462   (let* ((howm-view-l-i-matched nil)
1463          (evaluator (lambda (item)
1464                       (let ((str (funcall picker item)))
1465                         (if (string-match regexp str)
1466                             (progn
1467                               (setq howm-view-l-i-matched
1468                                     (if howm-view-l-i-matched 'multi 'single))
1469                               (if regexp-pos
1470                                   (match-string-no-properties regexp-pos str)
1471                                 1))
1472                           0))))
1473          (comparer (if regexp-pos
1474                        (lambda (x y)
1475                          (cond ((eq x 0) nil)
1476                                ((eq y 0) t)
1477                                (t (string< x y))))
1478                      #'>)))
1479     (let ((sorted-list (howm-sort-items evaluator comparer item-list
1480                                         reverse-p)))
1481       (cons howm-view-l-i-matched sorted-list))))
1482
1483 (defun howm-view-lift-by-name-internal (item-list regexp &optional reverse-p)
1484   (howm-view-lift-internal #'howm-view-item-basename
1485                            item-list regexp reverse-p))
1486
1487 (defun howm-view-lift-by-path-internal (item-list regexp &optional reverse-p)
1488   (howm-view-lift-internal #'howm-item-name item-list regexp reverse-p))
1489
1490 (defun howm-view-lift-by-summary-internal (item-list regexp &optional reverse-p)
1491   (howm-view-lift-internal #'howm-view-item-summary item-list regexp reverse-p))
1492
1493 (defun howm-view-lift-by-summary-substring-internal (item-list regexp
1494                                                                &optional
1495                                                                reverse-p
1496                                                                regexp-pos)
1497   (howm-view-lift-internal #'howm-view-item-summary item-list regexp reverse-p
1498                            (or regexp-pos 0)))
1499
1500 ;; backward compatibility
1501 (defalias 'howm-view-sort-by-name-match 'howm-view-lift-by-name)
1502 (defalias 'howm-view-sort-by-summary-match 'howm-view-lift-by-summary)
1503 (defalias 'howm-view-sort-by-summary-match-string
1504   'howm-view-lift-by-summary-substring)
1505
1506 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1507 ;;; Dired-X
1508
1509 (defvar howm-view-dired-buffer-name "*howm-dired*")
1510 (howm-defvar-risky howm-view-dired-ls-command "ls")
1511 (howm-defvar-risky howm-view-dired-ls-options '("-l"))
1512
1513 (defun dired-virtual (dir)
1514   (howm-inhibit-warning-in-compilation))
1515
1516 (defun howm-view-dired ()
1517   (interactive)
1518   (require (if (howm-xemacsp) 'dired-vir 'dired-x))
1519   (when (not (member major-mode
1520                      '(howm-view-summary-mode howm-view-contents-mode)))
1521     (error "Invalid mode for this command."))
1522 ;;   ;; bug in emacs-21.3.50?
1523 ;;   (when (not (fboundp 'dired-insert-headerline))
1524 ;;     (defun dired-insert-headerline (dir);; also used by dired-insert-subdir
1525 ;;       ;; Insert DIR's headerline with no trailing slash, exactly like ls
1526 ;;       ;; would, and put cursor where dired-build-subdir-alist puts subdir
1527 ;;       ;; boundaries.
1528 ;;       (save-excursion (insert "  " (directory-file-name dir) ":\n"))))
1529   (let* ((i2f (lambda (item)
1530                 (file-relative-name (howm-view-item-filename item))))
1531          (current-file (funcall i2f (riffle-summary-current-item)))
1532          (files (howm-cl-remove-duplicates* (mapcar i2f (howm-view-item-list))
1533                                             :test #'equal))
1534 ;;          (pos (cl-position f files :test #'string=))
1535          (args (append howm-view-dired-ls-options files))
1536          (a `((howm-view-summary-mode . ,howm-view-summary-persistent)
1537               (howm-view-contents-mode . ,howm-view-contents-persistent)))
1538          (p (howm-view-persistent-p (cdr (assoc major-mode a)))))
1539     (if p
1540         (howm-view-restore-window-configuration)
1541       (howm-view-kill-buffer))
1542     (switch-to-buffer (get-buffer-create howm-view-dired-buffer-name))
1543     (setq buffer-read-only nil)
1544     (erase-buffer)
1545     (howm-call-process-here howm-view-dired-ls-command args)
1546     (set-buffer-modified-p nil)
1547     (dired-virtual default-directory)
1548     (howm-view-dired-goto current-file)))
1549
1550 (defun howm-view-dired-goto (rname)
1551 "In dired buffer, search file name RNAME and move cursor to corresponding line.
1552 RNAME must be relative name."
1553   (goto-char (point-min))
1554   ;; Raw call of `dired-get-filename' and `dired-next-line' causes
1555   ;; warnings in compilation.
1556   (while (let ((c (howm-funcall-if-defined (dired-get-filename 'no-dir t))))
1557            (not (and c (equal (file-relative-name c) rname))))
1558     (howm-funcall-if-defined (dired-next-line 1))))
1559
1560 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1561 ;;; shell
1562
1563 (howm-defvar-risky howm-view-summary-shell-hist '("ls -l FILE" "FILE"))
1564 (howm-defvar-risky howm-view-summary-shell-last-file "FILE")
1565 (defun howm-view-summary-shell-command ()
1566   (interactive)
1567   (when (not (member major-mode
1568                      '(howm-view-summary-mode)))
1569     (error "Invalid mode for this command."))
1570   (let* ((n (howm-view-line-number))
1571          (item (nth (1- n) (howm-view-item-list)))
1572          (file (howm-page-abbreviate-name (howm-view-item-filename item)))
1573          (last-reg (regexp-quote howm-view-summary-shell-last-file)))
1574     (setq howm-view-summary-shell-hist
1575           (mapcar (lambda (h)
1576                     (replace-regexp-in-string last-reg file h t))
1577                   howm-view-summary-shell-hist))
1578     (setq howm-view-summary-shell-last-file file)
1579     (let* ((default (car howm-view-summary-shell-hist))
1580            (c (read-string "command: "
1581                            (cons default 0)
1582                            '(howm-view-summary-shell-hist . 1))))
1583       (shell-command c))
1584     (let ((item-list (cl-remove-if (lambda (item)
1585                                           (not (file-exists-p
1586                                                 (howm-view-item-filename item))))
1587                                         (howm-view-item-list))))
1588       (setq *riffle-summary-check* nil) ;; dirty
1589       (howm-view-summary (howm-view-name) item-list)
1590       (howm-goto-line n)
1591       (save-selected-window
1592         (let ((b (get-buffer "*Shell Command Output*")))
1593           (cond ((not (howm-buffer-empty-p b))
1594                  (switch-to-buffer-other-window b))
1595                 ((eq item (riffle-summary-current-item))
1596                  nil)
1597                 (t (progn
1598                      (setq *riffle-summary-check* t) ;; dirty
1599                      (howm-view-summary-check t))))))
1600       )))
1601
1602 ;;; howm-view.el ends here