X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?a=blobdiff_plain;f=lirc.el;h=77a2d6082cb5233d0b2645b729a0a8475d4cc6f8;hb=6b835f7f296aa17aad24fc8c60cf4267ae586a56;hp=c9dc91b2ed238a230141b0bdd8f634b5302cc0c8;hpb=9d8a20ea72ce8b050b69a33a7461e4a94db7691a;p=lurk.git diff --git a/lirc.el b/lirc.el index c9dc91b..77a2d60 100644 --- a/lirc.el +++ b/lirc.el @@ -1,4 +1,4 @@ -;;; lirc.el --- Lambdalabs irc client -*- lexical-binding:t -*- +;;; lirc.el --- Lightweight irc client -*- lexical-binding:t -*- ;; Copyright (C) 2021 Tim Vaughan @@ -28,7 +28,7 @@ ;;; Code: -(provide 'lerc) +(provide 'lirc) ;;; Customizations ;; @@ -48,15 +48,33 @@ (defcustom lirc-port 6667 "Default port.") -(defcustom lirc-prompt "> " +(defcustom lirc-prompt-string "> " "Prompt.") +;;; Faces +;; + +(defface lirc-prompt + '((t :inherit org-level-2)) + "Face used for the prompt.") + +(defface lirc-channel + '((t :inherit org-list-dt)) + "Face used for the channel name in the prompt.") + +;;; Global variables +;; + + +;;; Network process +;; + (defvar lirc-response "") (defun lirc-filter (proc string) (dolist (line (split-string (concat lirc-response string) "\n")) (if (string-suffix-p "\r" line) - (lirc-process-msg-string (string-trim line)) + (lirc-eval-msg-string (string-trim line)) (setq lirc-response line)))) (defun lirc-get-process () @@ -70,6 +88,9 @@ :nowait t :buffer "*lirc*")))) +;;; Messages +;; + (defun lirc-as-string (obj) (if obj (with-output-to-string (princ obj)) @@ -79,22 +100,33 @@ (list (lirc-as-string tags) (lirc-as-string src) (upcase (lirc-as-string cmd)) - (mapcar #'lirc-as-string params))) + (mapcar #'lirc-as-string + (if (and params (listp (elt params 0))) + (elt params 0) + params)))) (defun lirc-msg-tags (msg) (elt msg 0)) (defun lirc-msg-src (msg) (elt msg 1)) (defun lirc-msg-cmd (msg) (elt msg 2)) (defun lirc-msg-params (msg) (elt msg 3)) +(defun lirc-msg-trail (msg) + (let ((params (lirc-msg-params msg))) + (if params + (elt params (- (length params) 1))))) (defvar lirc-msg-regex (rx (opt (: "@" (group (* (not (or "\n" "\r" ";" " "))))) (* whitespace)) - (opt (: ":" (group (* (not (or "\n" "\r" " "))))) + (opt (: ":" (: (group (* (not (any space "!" "@")))) + (* (not (any space))))) (* whitespace)) (group (: (* (not whitespace)))) (* whitespace) - (opt (group (+ not-newline))))) + (opt (group (+ not-newline)))) + "Regex used to parse IRC messages. +Note that this regex is incomplete. Noteably, we discard the non-nick +portion of the source component of the message, as LIRC doesn't use this.") (defun lirc-string->msg (string) (if (string-match lirc-msg-regex string) @@ -112,36 +144,65 @@ (apply #'lirc-msg (append (list tags src cmd) params))) (error "Failed to parse string " string))) +(defun lirc--filtered-join (&rest args) + (string-join (seq-filter (lambda (el) el) args) " ")) + (defun lirc-msg->string (msg) (let ((tags (lirc-msg-tags msg)) (src (lirc-msg-src msg)) (cmd (lirc-msg-cmd msg)) (params (lirc-msg-params msg))) - (concat - (if tags (concat "@" tags " ") "") - (if src (concat ":" src " ") "") - cmd " " + (lirc--filtered-join + (if tags (concat "@" tags) nil) + (if src (concat ":" src) nil) + cmd (if (> (length params) 1) (string-join (seq-take params (- (length params) 1)) " ") - "") + nil) (if (> (length params) 0) - (concat " :" (elt params (- (length params) 1))))))) + (concat ":" (elt params (- (length params) 1))) + nil)))) + + +;;; Channels +;; + +(defvar lirc-current-channel nil) +(defvar lirc-channel-list nil) + +(defun lirc-add-channel (channel-name) + (add-to-list 'lirc-channel-list + (list channel-name))) + +(defun lirc-del-channel (channel-name) + (setq lirc-channel-list + (assoc-delete-all channel-name lirc-channel-list))) + +(defun lirc-get-channel-users (channel-name) + (cdr (assoc channel-name lirc-channel-list))) + +(defun lirc-set-channel-users (channel-name users) + (setcdr (assoc channel-name lirc-channel-list) users)) + +(defun lirc-add-channel-users (channel-name &rest users) + (let ((current-users (lirc-channel-get-users channel-name))) + (lirc-channel-set-users channel-name (append users current-users)))) + +(defun lirc-del-channel-users (channel-name &rest users) + (let ((current-users (lirc-get-channel-users channel-name))) + (lirc-channel-set-users channel-name + (cl-set-difference current-users users :test #'equal)))) + + +;;; Buffer +;; (defun lirc-display-string (string) - (with-current-buffer "*lirc*" - (let ((inhibit-read-only t)) - (save-excursion - (goto-char (point-max)) - (insert string "\n"))))) - -(defun lirc-process-msg-string (string) - (let ((msg (lirc-string->msg string))) - (cond - ((equal (lirc-msg-cmd msg) "PING") - (lirc-send-msg - (lirc-msg nil nil "PONG" (lirc-msg-params msg)))) - (t - (lirc-display-string (lirc-msg->string msg)))))) + (with-current-buffer (get-buffer-create "*lirc*") + (save-excursion + (goto-char lirc-prompt-marker) + (let ((inhibit-read-only t)) + (insert-before-markers (propertize (concat string "\n") 'read-only t)))))) (defun lirc-connect () (lirc-send-msg (lirc-msg nil nil "USER" lirc-user-name 0 "*" lirc-full-name)) @@ -150,18 +211,149 @@ (defun lirc-send-msg (msg) (let ((proc (lirc-get-process))) (process-send-string proc (concat (lirc-msg->string msg) "\r\n")))) + +(defun lirc-render-prompt () + (with-current-buffer "*lirc*" + (set-marker-insertion-type lirc-prompt-marker nil) + (set-marker-insertion-type lirc-input-marker t) + (save-excursion + (let ((inhibit-read-only t)) + (delete-region lirc-prompt-marker lirc-input-marker) + (goto-char lirc-prompt-marker) + (insert + (propertize (if lirc-current-channel + lirc-current-channel + "[no channel]") + 'face 'lirc-channel + 'read-only t) + (propertize lirc-prompt-string + 'face 'lirc-prompt + 'read-only t + 'rear-nonsticky t))))) + (set-marker-insertion-type lirc-input-marker nil)) +(defvar lirc-prompt-marker nil + "Marker for prompt position in LIRC buffer.") + +(defvar lirc-input-marker nil + "Marker for prompt position in LIRC buffer.") + +(defun lirc-setup-buffer () + (with-current-buffer (get-buffer-create "*lirc*") + (if (markerp lirc-prompt-marker) + (set-marker lirc-prompt-marker (point-max)) + (setq lirc-prompt-marker (point-max-marker))) + (if (markerp lirc-input-marker) + (set-marker lirc-input-marker (point-max)) + (setq lirc-input-marker (point-max-marker))) + (lirc-render-prompt) + (goto-char (point-max)) + (recenter -1))) + +;;; Message evaluation +;; + +(defun lirc-eval-msg-string (string) + (lirc-display-string string) + (let* ((msg (lirc-string->msg string))) + (pcase (lirc-msg-cmd msg) + ("PING" + (lirc-send-msg + (lirc-msg nil nil "PONG" (lirc-msg-params msg)))) + ;; ((rx (= 3 digit)) + ;; (lirc-display-string (string-join (cdr (lirc-msg-params msg)) " "))) + + ("353" ; NAMEREPLY + (let* ((params (lirc-msg-params msg)) + (channel (elt params 2)) + (names (split-string (elt params 3)))) + (apply #'lirc-add-channel-users (cons channel names)))) + + ((and "JOIN" + (guard (equal lirc-nick (lirc-msg-src msg)))) + (let ((channel (car (lirc-msg-params msg)))) + (setq lirc-current-channel channel) + (lirc-add-channel channel) + (lirc-render-prompt))) + + ("JOIN" + (let ((channel (car (lirc-msg-params msg))) + (nick (lirc-msg-src msg))) + (lirc-add-channel-users channel nick))) + + ((and "PART" + (guard (equal lirc-nick (lirc-msg-src msg)))) + (setq lirc-current-channel nil) + (lirc-del-channel (car (lirc-msg-params msg)))) + + ("PART" + (let ((channel (car (lirc-msg-params msg))) + (nick (lirc-msg-src msg))) + (lirc-del-channel-users channel nick))) + + ((and "NICK" + (guard (equal lirc-nick (lirc-msg-src msg)))) + (setq lirc-nick (car (lirc-msg-params msg))) + (lirc-display-string (concat "*** Set nick to " lirc-nick))) + + ("PRIVMSG" + (let ((nick (lirc-msg-src msg)) + (channel (car (lirc-msg-params msg))) + (text (cadr (lirc-msg-params msg)))) + (lirc-display-string (concat channel " <" nick "> " text)))) + (_ + (lirc-display-string (lirc-msg->string msg)))))) + + +;;; Command entering +;; + +(defun lirc-enter-string (string) + (if (string-prefix-p "/" string) + (pcase (substring string 1) + (cmd-str + (lirc-send-msg (lirc-msg nil nil cmd-str)))) + (error "Not implemented."))) + +(defun lirc-enter () + "Enter current contents of line after prompt." + (interactive) + (with-current-buffer "*lirc*" + (lirc-enter-string + (buffer-substring lirc-input-marker (point-max))) + (let ((inhibit-read-only t)) + (delete-region lirc-input-marker (point-max))))) + + + +;;; Mode +;; + +(defvar lirc-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") 'lirc-enter) + map)) + +(define-derived-mode lirc-mode text-mode "lirc" + "Major mode for LIRC.") + +(when (fboundp 'evil-set-initial-state) + (evil-set-initial-state 'lirc-mode 'insert)) + +;;; Main start procedure +;; (defun lirc () "Switch to *lirc* buffer." (interactive) - (pop-to-buffer-same-window "*lirc*") + (if (get-buffer "*lirc*") + (switch-to-buffer "*lirc*") + (switch-to-buffer "*lirc*")) (lirc-mode) - (lirc-connect)) + (lirc-setup-buffer) + (lirc-connect) + "Started LIRC.") -(define-derived-mode lirc-mode text-mode "lirc" - "Major mode for LIRC.") ;;; lirc.el ends here -:bs-mbpr348.d.ethz.ch