* liece-misc.el: Don't require pccl and broken.
[elisp/liece.git] / lisp / liece-misc.el
1 ;;; liece-misc.el --- Miscellaneous routines.
2 ;; Copyright (C) 1998-2000 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Created: 1998-09-28
6 ;; Revised: 1998-11-25
7 ;; Keywords: IRC, liece
8
9 ;; This file is part of Liece.
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26
27 ;;; Commentary:
28 ;; 
29
30 ;;; Code:
31
32 (eval-and-compile
33   (require 'invisible)
34   (require 'liece-inlines)
35   (require 'liece-coding))
36
37 (eval-when-compile
38   (autoload '_ "liece-intl" nil nil 'macro))
39
40 (defun liece-toggle-command-buffer-mode (&optional mode)
41   "Toggle command buffer MODE."
42   (let ((mode
43          (or mode
44              (if (eq liece-command-buffer-mode 'chat)
45                  'channel
46                'chat)))
47         (hide (get 'liece-nick-buffer-mode 'hide)))
48     (cond
49      ((eq mode 'chat)
50       (put 'liece-nick-buffer-mode 'hide t)
51       (setq liece-private-indicator "P"))
52      (t
53       (put 'liece-nick-buffer-mode 'hide nil)
54       (setq liece-private-indicator "-")))
55     (and (not (eq liece-command-buffer-mode mode))
56          (not (eq hide (get 'liece-nick-buffer-mode 'hide)))
57          liece-nick-window-auto-hide
58          (liece-configure-windows))
59     (setq liece-command-buffer-mode mode)
60     liece-command-buffer-mode))
61
62 (defsubst liece-set-frame-title-format ()
63   "Inline function for modifying `frame-title-format'."
64   (let ((frame-indicator liece-channel-indicator))
65     (when (eq liece-command-buffer-mode 'channel)
66       (if liece-display-status-on-channel-indicator
67           (setq frame-indicator liece-channel-indicator)
68         (setq frame-indicator
69               (format "%s: %s [%s]"
70                       liece-channel-indicator
71                       (or (and liece-current-channel
72                                (liece-channel-get-topic))
73                           "")
74                       (or (and liece-current-channel
75                                (liece-channel-get-modes))
76                           "")))))
77     (setq liece-channel-status-indicator frame-indicator)))
78
79 (defsubst liece-set-channel-indicator ()
80   "Inline-function for modifying `liece-channel-indicator'."
81   (if (eq liece-command-buffer-mode 'chat)
82       (setq liece-channel-indicator
83             (if liece-current-chat-partner
84                 (format (_ "Chatting with %s")
85                         liece-current-chat-partner)
86               (_ "No partner")))
87     (setq liece-channel-indicator
88           (if liece-current-channel
89               (concat liece-current-channel
90                       (if liece-display-status-on-channel-indicator
91                           (format ": %s [%s]"
92                                   (or (and liece-current-channel
93                                            (liece-channel-get-topic))
94                                       "")
95                                   (or (and liece-current-channel
96                                            (liece-channel-get-modes))
97                                       ""))
98                         ""))
99             (_ "No channel"))))
100   (with-current-buffer liece-command-buffer
101     (force-mode-line-update))
102   (if liece-display-frame-title
103       (liece-set-frame-title-format)))
104
105 (defun liece-set-beep (buffer &optional arg)
106   (with-current-buffer buffer
107     (setq liece-beep (if arg (plusp arg) (not liece-beep))
108           liece-beep-indicator (if liece-beep "B" "-"))
109     (force-mode-line-update)))
110
111 (defmacro liece-beep (&optional arg)
112   (list 'funcall 'liece-beep-function arg))
113
114 (defun liece-freeze (buffer &optional arg)
115   (with-current-buffer buffer
116     (setq liece-freeze (if arg (plusp arg) (not liece-freeze))
117           liece-freeze-indicator (if liece-freeze "F" "-"))
118     (force-mode-line-update)))
119
120 (defmacro liece-frozen (buffer)
121   (list 'with-current-buffer buffer 'liece-freeze))
122
123 (defun liece-own-freeze (buffer &optional arg)
124   (with-current-buffer buffer
125     (setq liece-own-freeze (if arg (plusp arg) (not liece-own-freeze))
126           liece-own-freeze-indicator (if liece-own-freeze "M" "-"))
127     (force-mode-line-update)))
128
129 (defmacro liece-own-frozen (buffer)
130   (list 'with-current-buffer buffer 'liece-own-freeze))
131
132 (defun liece-ignore-this-p (nick user-at-host)
133   ;; Remove entries which are expired.
134   (let ((time (current-time)) expire-time)
135     (dolist (kill liece-kill-nickname)
136       (setq expire-time (if (cdr kill)
137                             (liece-time-difference time (cdr kill))
138                           1))
139       (when (< expire-time 0)
140         (if (zerop (cadddr kill))
141             (liece-insert-info liece-D-buffer
142                                (format (_ "Ignore timeout for %s expired.\n")
143                                        (car kill))))
144         (when (setq kill (string-assoc-ignore-case
145                           (car kill) liece-kill-nickname))
146           (setq liece-kill-nickname (delq kill liece-kill-nickname)
147                 liece-save-variables-are-dirty t)))))
148   ;; Search on `liece-kill-nickname' and return non-nil if matches.
149   (unless (run-hook-with-args-until-success
150            'liece-custom-ignore-this-p nick user-at-host)
151     (let ((case-fold-search t))
152       (member-if
153        (lambda (kill)
154          (or (liece-nick-equal (car kill) nick)
155              (string-match (concat "\\<" (car kill) "\\>") nick)
156              (and (string-match "@" (car kill))
157                   (or (string-equal-ignore-case
158                        (car kill) user-at-host)
159                       (string-match (concat "^" (car kill) "$")
160                                     user-at-host)))))
161        liece-kill-nickname))))
162
163 (defun liece-split-line (line)
164   (cond
165    ((eq ?: (aref line 0))
166     (list (substring line 1)))
167    (t
168     (let (args)
169       (catch 'done
170         (while (string-match "^\\([^ ]+\\) +" line)
171           (setq args (nconc args (list (match-string 1 line)))
172                 line (substring line (match-end 0)))
173           (and (not (string= "" line)) (eq ?: (aref line 0))
174                (setq line (substring line 1))
175                (throw 'done nil))))
176       (or (string= "" line)
177           (setq args (nconc args (list line))))
178       args))))
179
180 (defmacro liece-message (&rest message)
181   `(message "%s: %s"
182             (product-name (product-find 'liece-version))
183             (format ,@message)))
184
185 (defmacro liece-insert-change (buffer msg)
186   `(liece-insert ,buffer (concat liece-change-prefix ,msg)))
187
188 (defmacro liece-insert-notice (buffer msg)
189   `(liece-insert ,buffer (concat liece-notice-prefix ,msg)))
190
191 (defmacro liece-insert-broadcast (buffer msg)
192   `(liece-insert ,buffer (concat liece-broadcast-prefix ,msg)))
193
194 (defmacro liece-insert-wallops (buffer msg)
195   `(liece-insert ,buffer (concat liece-wallops-prefix ,msg)))
196
197 (defmacro liece-insert-error (buffer msg)
198   `(liece-insert ,buffer (concat liece-error-prefix ,msg)))
199
200 (defmacro liece-insert-info (buffer msg)
201   `(liece-insert ,buffer (concat liece-info-prefix ,msg)))
202
203 (defmacro liece-insert-timestamp (buffer msg)
204   `(liece-insert ,buffer (concat liece-timestamp-prefix ,msg)))
205
206 (defmacro liece-insert-dcc (buffer msg)
207   `(liece-insert ,buffer (concat liece-dcc-prefix ,msg)))
208
209 (defmacro liece-insert-client (buffer msg)
210   `(liece-insert ,buffer (concat liece-client-prefix ,msg)))
211
212 (defmacro liece-own-message (message)
213   `(if (eq liece-command-buffer-mode 'channel)
214        (liece-own-channel-message ,message)
215      (liece-own-channel-message ,message)))
216
217 (defmacro liece-own-channel-message (message &optional chnl)
218   `(let* ((chnl (or ,chnl (liece-current-channel)))
219           (liece-message-target chnl)
220           (liece-message-speaker (liece-current-nickname))
221           (liece-message-direction 'outgoing))
222      (liece-display-message ,message)))
223
224 (defmacro liece-own-private-message (message &optional partner)
225   `(let* ((partner (or ,partner liece-current-chat-partner))
226           (liece-message-target partner)
227           (liece-message-speaker (liece-current-nickname))
228           (liece-message-direction 'outgoing))
229      (liece-display-message ,message)))
230
231 (defmacro liece-convert-received-input (input)
232   "Convert input before it is processed"
233   `(let ((conv-list liece-receive-convert-list)
234          (input ,input)
235          i f s s1 s2)
236      (while (and conv-list (not liece-polling))
237        (setq i (car conv-list)
238              f (car i)
239              s (cadr i)
240              s1 (if (stringp f) f (funcall f input))
241              s2 (if (stringp s) s (funcall s s1))
242              input (replace-in-string input s1 s2)
243              conv-list (cdr conv-list)))
244      input))
245
246 (defun liece-send (&rest args)
247   "Send message to IRC server."
248   (liece-reset-idle)
249   (let ((string (apply #'format args)) send-string len)
250     (dolist (convert liece-send-convert-list)
251       (setq string (apply #'replace-in-string string convert)))
252     (with-current-buffer liece-command-buffer
253       (setq send-string (liece-coding-encode-charset-string string)
254             send-string (if (string-match "\r$" send-string) send-string
255                           (concat send-string "\r\n"))
256             len (length send-string)))
257     (if (< len 512)
258         (process-send-string liece-server-process send-string)
259       (message "Protocol message too long (%d).  Truncated." len)
260       (if liece-beep-on-bells (beep)))
261     (if (string-match "^list\\s-*" (setq string (downcase string)))
262         (setq liece-channel-filter (substring string (match-end 0))))))
263
264 (defmacro liece-send-pong ()
265   '(liece-send "PONG :%s" liece-tmp-server-name))
266
267 (defmacro liece-increment-long-reply-count ()
268   '(incf liece-long-reply-count))
269
270 (defmacro liece-reset-long-reply-count ()
271   '(setq liece-long-reply-count 0))
272
273 (defmacro liece-check-long-reply-count ()
274   '(when (> liece-long-reply-count liece-long-reply-max)
275      (liece-reset-long-reply-count)
276      (liece-send-pong)))
277
278 (defmacro liece-server-host ()
279   '(if (listp liece-server)
280        (plist-get liece-server ':host)
281      (if (or (string-match "^\\[\\([^]]+\\)\\]:?[0-9]*" liece-server)
282              (string-match "^\\([^:]+\\):?[0-9]*" liece-server))
283          (match-string 1 liece-server)
284        liece-server)))
285
286 (defmacro liece-clean-hostname (hostname)
287   "Return the arg HOSTNAME, but if is a dotted-quad, put brackets around it."
288   `(save-match-data
289      (if (string-match "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+" ,hostname)
290          (concat "[" ,hostname "]")
291        ,hostname)))
292
293 (defun liece-current-nickname ()
294   "Return the current nickname."
295   liece-real-nickname)
296
297 (defun liece-current-channel ()
298   "Return the current channel."
299   liece-current-channel)
300
301 (defun liece-current-channels ()
302   "Return the current channels."
303   liece-current-channels)
304
305 (defun liece-current-chat-partner ()
306   "Return the current chat partner."
307   liece-current-chat-partner)
308
309 (defun liece-current-chat-partners ()
310   "Return the current chat partners."
311   liece-current-chat-partners)
312
313 (defmacro liece-scroll-if-visible (window)
314   `(if ,window (set-window-point ,window (point-max))))
315
316 (defmacro liece-pick-buffer-1 (chnl)
317   `(cdr (string-assoc-ignore-case ,chnl liece-channel-buffer-alist)))
318
319 (defun liece-pick-buffer (chnl)
320   (cond
321    ((stringp chnl)
322     (let ((buf (liece-pick-buffer-1 chnl)))
323       (if buf (list buf))))
324    ((and chnl (listp chnl))
325     (let ((buf (liece-pick-buffer-1 (car chnl))))
326       (if buf (cons buf (liece-pick-buffer (cdr chnl))))))
327    (t nil)))
328
329 \f
330 ;;; Date and time handling functions
331 (defun liece-compose-time-string (time)
332   (format-time-string "%A %B %e %Y %R" time))
333
334 (defun liece-convert-seconds (time)
335   "Convert seconds to printable string."
336   (let* ((seconds (string-to-int time))
337          (minutes (/ seconds 60))
338          (seconds (if minutes (% seconds 60) seconds))
339          (hours (/ minutes 60))
340          (minutes (if hours (% minutes 60) minutes))
341          (days (/ hours 24))
342          (hours (if days (% hours 24) hours))
343          (ds (and (/= 0 days)
344                   (format "%d day%s, " days
345                           (if (> days 1) "s" ""))))
346          (hs (and (/= 0 hours)
347                   (format "%d hour%s, " hours
348                           (if (> hours 1) "s" ""))))
349          (ms (and (/= 0 minutes)
350                   (format "%d minute%s " minutes
351                           (if (> minutes 1) "s" ""))))
352          (ss (format "%d seconds" seconds)))
353     (concat ds hs ms (if seconds ss ""))))
354
355 (defmacro liece-insert-time-string ()
356   '(insert (substring (current-time-string) 11 16) " "))
357
358 (defvar liece-idle-point nil "Timestamp of last idle reset.")
359
360 (defmacro liece-reset-idle ()
361   "Reset idle counter and return last idle."
362   '(prog1 (liece-idle) (setq liece-idle-point (current-time))))
363
364 (defmacro liece-idle ()
365   "How long has liece been idle."
366   '(if liece-idle-point
367        (liece-time-difference liece-idle-point (current-time))
368      9999999))
369
370 (defmacro liece-ping-if-idle (&optional limit)
371   `(if (<= (liece-idle) (or ,limit 120))
372        nil
373      (liece-command-ping)
374      t))
375
376 (defmacro liece-maybe-poll ()
377   '(liece-send "PING %s" (system-name)))
378
379 (defun liece-get-buffer-create (name)
380   "Get or create buffer, keep track on its NAME so we can kill it."
381   (let ((buffer (get-buffer-create name)))
382     (or (memq buffer liece-buffer-list)
383         (push buffer liece-buffer-list))
384     buffer))
385
386 (defmacro liece-message-from-ignored (prefix rest)
387   `(save-excursion
388      (liece-insert liece-I-buffer (concat ,prefix "::" ,rest "\n"))
389      t))
390
391 (defmacro liece-is-message-ignored (string buffer)
392   `(let (found (case-fold-search t) msg str msgstr who)
393      (catch 'ignore
394        (when (member ,buffer liece-no-ignore-buffers)
395          (throw 'ignore t))
396        (dolist (ignore-entry liece-ignore-list)
397          ;; Check message type
398          (cond
399           ((consp (car ignore-entry))
400            (setq msg (caar ignore-entry)
401                  str (cdar ignore-entry)))
402           ((fboundp (car ignore-entry))
403            (setq msgstr (apply (car ignore-entry) (list ,string))
404                  msg (car msgstr)
405                  str (cdr msgstr)))
406           (t
407            (liece-message
408             (_ "Malformed ignore-list, no msg+str function."))))
409          ;; Check message from whom
410          (cond
411           ((listp (cadr ignore-entry))
412            (setq who (cadr ignore-entry)))
413           ((fboundp (cadr ignore-entry))
414            (setq who (apply (cadr ignore-entry) (list ,string))))
415           ((not (cadr ignore-entry))
416            (liece-message
417             (_ "Malformed ignore-list, no user function."))))
418          ;; Handle regexp
419          (save-match-data
420            (when (and (or msg str)
421                       (and msg
422                            (string-match
423                             msg (cadr liece-current-function)))
424                       (and str (string-match str ,string)))
425              (while who
426                (when (string-match (car who) (car liece-current-function))
427                  (setq found t)
428                  (throw 'ignore t))
429                (setq who (cdr who)))))))
430      found))
431
432 ;;; stolen (and renamed) from time-date.el.
433 (defun liece-subtract-time (t1 t2)
434   "Subtract two internal times."
435   (let ((borrow (< (cadr t1) (cadr t2))))
436     (list (- (car t1) (car t2) (if borrow 1 0))
437           (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))
438
439 (defun liece-time-difference (t1 t2)
440   "Return the differnce between two internal times in seconds."
441   (let ((sub (liece-subtract-time t1 t2)))
442     (+ (* (car sub) 65536) (cadr sub))))
443
444 (defun liece-time-elapsed (time seconds)
445   "Add SECONDS to TIME."
446   (list (+ (car time) (/ (+ (cadr time) seconds) 65536))
447         (% (+ (cadr time) seconds) 65536)
448         (nth 2 time)))
449
450 ;;; stolen (and renamed) from time-date.el.
451 (defun liece-seconds-to-time (seconds)
452   "Convert SECONDS (a floating point number) to an Emacs time structure."
453   (list (floor seconds 65536)
454         (floor (mod seconds 65536))
455         (floor (* (- seconds (ffloor seconds)) 1000000))))
456
457 (defun liece-generate-hex-timestamp (&optional time)
458   "Generate timestamp string as hexadecimal.
459 If optional argument TIME is nil, calculate timestamp using current time."
460   (or time
461       (setq time (current-time)))
462   (format "%04x%04x" (car time) (cadr time)))
463
464 (defmacro liece-hex-timestamp-valid (timestamp limit)
465   "Is TIMESTAMP valid within LIMIT?"
466   `(let (t1 t2 diff (timestamp ,timestamp))
467      (if (not (and (stringp timestamp)
468                    (string-match
469                     "^[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$" timestamp)))
470          nil
471        (setq t1 (liece-hex-string-to-integer (substring timestamp 0 4))
472              t2 (liece-hex-string-to-integer (substring timestamp 4 8))
473              diff (liece-time-difference
474                    (list t1 t2 0) (current-time)))
475        (or (>= ,limit 0)
476            (and (< diff ,limit) (> diff (- 0 ,limit)))))))
477
478 (defmacro liece-hex-char-to-integer (character)
479   "Convert single hex digit CHARACTER to integer."
480   `(if (and (>= ,character ?0) (<= ,character ?9))
481        (- ,character ?0)
482      (let ((ch (logior ,character 32)))
483        (if (and (>= ch ?a) (<= ch ?f))
484            (- ch (- ?a 10))
485          (error "Invalid hex digit `%c'" ch)))))
486
487 (defmacro liece-hex-string-to-integer (hex-string)
488   "Convert a HEX-STRING like ffff to the decimal integer."
489   `(let ((hex-string ,hex-string) (hex-num 0))
490      (while (not (equal hex-string ""))
491        (setq hex-num (+ (* hex-num 16)
492                         (liece-hex-char-to-integer
493                          (string-to-char hex-string))))
494        (setq hex-string (substring hex-string 1)))
495      hex-num))
496
497 (defmacro liece-remove-properties-region (start end)
498   (unless (fboundp 'make-extent)
499     `(save-excursion
500        (save-restriction
501          (narrow-to-region ,start ,end)
502          (goto-char (point-min))
503          (let (start)
504            (while (setq start (next-single-property-change
505                                (point) 'invisible))
506              (when (invisible-p start)
507                (delete-region start (next-visible-point start))
508                (goto-char start))
509              (remove-text-properties (point-min)(point-max) '(face))))))))
510
511 (provide 'liece-misc)
512
513 ;;; liece-misc.el ends here