-;;; lurk.el --- Little Uni-buffer iRc Klient -*- lexical-binding:t -*-
+;;; lurk.el --- Little Unibuffer iRc Klient -*- lexical-binding:t -*-
;; Copyright (C) 2021 Tim Vaughan
;;
(defgroup lurk nil
- "Little Uni-buffer iRc Klient."
+ "Little Unibuffer iRc Klient."
:group 'network)
(defcustom lurk-nick "plugd"
(defvar lurk-debug nil
"If non-nil, enable debug mode.")
+
+;;; Utility procedures
+;;
+
+(defun lurk--filtered-join (&rest args)
+ (string-join (seq-filter (lambda (el) el) args) " "))
+
+(defun lurk--as-string (obj)
+ (if obj
+ (with-output-to-string (princ obj))
+ nil))
+
+
;;; Network process
;;
;;; Server messages
;;
-(defun lurk--as-string (obj)
- (if obj
- (with-output-to-string (princ obj))
- nil))
-
(defun lurk-msg (tags src cmd &rest params)
(list (lurk--as-string tags)
(lurk--as-string src)
(apply #'lurk-msg (append (list tags src cmd) params)))
(error "Failed to parse string " string)))
-(defun lurk--filtered-join (&rest args)
- (string-join (seq-filter (lambda (el) el) args) " "))
-
(defun lurk-msg->string (msg)
(let ((tags (lurk-msg-tags msg))
(src (lurk-msg-src msg))
"Marker for prompt position in LURK buffer.")
(defun lurk-setup-header ()
- (setq-local header-line-format
- '(:eval
- (let ((proc (get-process "lurk")))
- (if proc
- (concat
- "Host: " (car (process-contact proc))
- ", Context: "
- (if lurk-current-context
- (concat
- lurk-current-context
- " ("
- (number-to-string
- (length (lurk-get-context-users lurk-current-context)))
- " users)")
- "Server"))
- "No connection")))))
+ (with-current-buffer "*lurk*"
+ (setq-local header-line-format
+ '((:eval
+ (let ((proc (get-process "lurk")))
+ (if proc
+ (concat
+ "Host: " (car (process-contact proc))
+ ", Context: "
+ (if lurk-current-context
+ (concat
+ lurk-current-context
+ " ("
+ (number-to-string
+ (length (lurk-get-context-users lurk-current-context)))
+ " users)")
+ "Server"))
+ "No connection")))
+ (:eval
+ (if lurk-zoomed " [ZOOMED]" ""))))))
(defun lurk-setup-buffer ()
(with-current-buffer (get-buffer-create "*lurk*")
;;; Output formatting and highlighting
;;
-;; Partially-implemented idea: the face text property can be
-;; a list of faces, applied in order. By assigning each context
-;; a unique list and keeping track of these in a hash table, we can
-;; easily switch the face corresponding to a particular context
-;; by modifying the elements of this list.
+;; Idea: the face text property can be a list of faces, applied in
+;; order. By assigning each context a unique list and keeping track
+;; of these in a hash table, we can easily switch the face
+;; corresponding to a particular context by modifying the elements of
+;; this list.
;;
;; More subtly, we make only the cdrs of this list shared among
;; all text of a given context, allowing the cars to be different
'read-only t
'context context
'invisible context-atom)
- (propertize (concat (lurk-buttonify-urls (apply #'concat strings)) "\n")
- 'face (lurk-get-context-facelist context)
- 'read-only t
- 'context context
- 'invisible context-atom)))))))
+ (lurk-add-formatting
+ (propertize (concat (apply #'lurk-buttonify-urls strings) "\n")
+ 'face (lurk-get-context-facelist context)
+ 'read-only t
+ 'context context
+ 'invisible context-atom))))))))
(defun lurk-display-message (from to text)
(let ((context (if (eq 'channel (lurk-get-context-type to))
(opt (group (: ":" (+ digit))))
(opt (group (: "/"
(opt
- (* (any alnum "-/.,#:%=&_"))
- (any alnum "-/#:%=&_")))))))
+ (* (any alnum "-/.,#:%=&_?~@+"))
+ (any alnum "-/#:%=&_~@+")))))))
"Imperfect regex used to find URLs in plain text.")
(defun lurk-click-url (button)
(browse-url (button-get button 'url)))
-(defun lurk-buttonify-urls (string)
+(defun lurk-buttonify-urls (&rest strings)
"Turn substrings which look like urls in STRING into clickable buttons."
(with-temp-buffer
- (insert string)
+ (apply #'insert strings)
(goto-char (point-min))
(while (re-search-forward lurk-url-regex nil t)
(let ((url (match-string 0)))
'help-echo "Open URL in browser.")))
(buffer-string)))
+(defun lurk-add-formatting (string)
+ (with-temp-buffer
+ (insert string)
+ (goto-char (point-min))
+ (let ((bold nil)
+ (italics nil)
+ (underline nil)
+ (strikethrough nil)
+ (prev-point (point)))
+ (while (re-search-forward (rx (any "\x02\x1D\x1F\x1E")) nil t)
+ (let ((beg (+ (match-beginning 0) 1)))
+ (if bold
+ (add-face-text-property prev-point beg '(:weight bold)))
+ (if italics
+ (add-face-text-property prev-point beg '(:slant italic)))
+ (if underline
+ (add-face-text-property prev-point beg '(:underline t)))
+ (if strikethrough
+ (add-face-text-property prev-point beg '(:strike-through t)))
+ (pcase (match-string 0)
+ ("\x02" (setq bold (not bold)))
+ ("\x1D" (setq italics (not italics)))
+ ("\x1F" (setq underline (not underline)))
+ ("\x1E" (setq strikethrough (not strikethrough))))
+ (delete-region (match-beginning 0) (match-end 0))
+ (setq prev-point (point)))))
+ (buffer-string)))
+
;;; Message evaluation
;;
(if lurk-debug
(lurk-display-string nil nil string))
(let* ((msg (lurk-string->msg string)))
+ (lurk-process-autoreplies msg)
(pcase (lurk-msg-cmd msg)
("PING"
(lurk-send-msg
(lurk-display-notice nil (lurk-msg->string msg))))))
+;;; User-defined responses
+
+
+(defvar lurk-autoreply-table nil
+ "Table of autoreply messages.
+
+Each autoreply is a list of two elements: (matcher reply)
+
+Here matcher is a list:
+
+(network src cmd params ...)
+
+and reply is another list:
+
+ (cmd params ...)
+
+Each entry in the matcher list is a regular expression tested against the
+corresponding values in the incomming message. Entries can be nil,
+in which case they match anything.")
+
+(defun lurk--lists-equal (l1 l2)
+ (if (and l1 l2)
+ (if (or (not (and (car l1) (car l2)))
+ (string-match (car l1) (car l2)))
+ (lurk--lists-equal (cdr l1) (cdr l2))
+ nil)
+ t))
+
+(defun lurk-process-autoreply (msg autoreply)
+ (let ((matcher (car autoreply))
+ (reply (cadr autoreply)))
+ (let ((network (car matcher)))
+ (when (and (or (not network)
+ (and (get-process "lurk")
+ (equal (car (process-contact (get-process "lurk")))
+ (cadr (assoc network lurk-networks)))))
+ (lurk--lists-equal (cdr matcher)
+ (append (list (lurk-msg-src msg)
+ (lurk-msg-cmd msg))
+ (lurk-msg-params msg))))
+ (lurk-send-msg
+ (lurk-msg nil nil (car reply) (cdr reply)))))))
+
+(defun lurk-process-autoreplies (msg)
+ (mapc
+ (lambda (autoreply)
+ (lurk-process-autoreply msg autoreply))
+ lurk-autoreply-table))
+
;;; Command entering
;;
(defun lurk-enter-string (string)
(if (string-prefix-p "/" string)
(pcase (substring string 1)
- ((rx "DEBUG")
- (setq lurk-debug (not lurk-debug))
+ ((rx (: "DEBUG" (opt (: " " (let setting (or "ON" "OFF"))))))
+ (setq lurk-debug
+ (if setting
+ (if (equal (upcase setting) "ON")
+ t
+ nil)
+ (not lurk-debug)))
(lurk-display-notice nil "Debug mode now " (if lurk-debug "on" "off") "."))
((rx "HEADER")