1808 lines
69 KiB
EmacsLisp
1808 lines
69 KiB
EmacsLisp
;;; weechat --- Chat via WeeChat's relay protocol in Emacs ;; -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2013 Moritz Ulrich
|
|
|
|
;; Author: Moritz Ulrich <moritz@tarn-vedra.de>
|
|
;; Rüdiger Sonderfeld <ruediger@c-plusplus.de>
|
|
;; Aristid Breitkreuz <aristidb@gmail.com>
|
|
;; Keywords: irc chat network weechat
|
|
;; URL: https://github.com/the-kenny/weechat.el
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
;; This package provides a way to chat via WeeChat's relay protocol in
|
|
;; Emacs.
|
|
|
|
;; Please see README.org on how to use it.
|
|
|
|
;;; Code:
|
|
|
|
(require 'weechat-core)
|
|
(require 'weechat-relay)
|
|
(require 'weechat-color)
|
|
(require 'cl-lib)
|
|
(require 'format-spec)
|
|
(require 's)
|
|
|
|
(defcustom weechat-host-default "localhost"
|
|
"Default host for `weechat-connect'."
|
|
:type 'string
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-port-default 9000
|
|
"Default port for `weechat-connect'."
|
|
:type 'string
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-mode-default 'plain
|
|
"Wether to connect via SSL by default.
|
|
|
|
Null or 'plain: Plain socket.
|
|
t or 'ssl: TLS socket.
|
|
String: command to run."
|
|
:type '(choice
|
|
(const :tag "Plain" 'plain)
|
|
(const :tag "SSL/TLS" 'ssl)
|
|
(string :tag "Command to run"))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-modules '(weechat-button weechat-complete)
|
|
"Modules loaded when weechat.el is loaded.
|
|
|
|
Each module must be in `load-path' and must have a call to
|
|
provide in order to be loaded correctly.
|
|
|
|
To unload modules, use (unload-feature FEATURE)."
|
|
:type '(repeat symbol)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-read-only t
|
|
"Whether to make text in weechat buffers read-only."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-initial-lines 100
|
|
"Number of lines to show when initializing a channel buffer."
|
|
:type 'integer
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-more-lines-amount 10
|
|
"Number of extra lines `weechat-get-more-lines' will retieve."
|
|
:type 'integer
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-prompt "[%n] "
|
|
"The Weechat prompt."
|
|
:type 'string
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-buffer-line-limit 1000
|
|
"Number of max. lines per buffer."
|
|
:type '(choice integer
|
|
(const :tag "Unlimited" nil))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-return-always-replace-input t
|
|
"Always replace current input with line on return.
|
|
|
|
If set to t, pressing return will always copy the current line to
|
|
the input prompt. If nil, only copy when the input line is
|
|
empty."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-move-cursor-to-prompt t
|
|
"Automatically move the cursor to the prompt when typing."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-recenter t
|
|
"Wether the prompt will always stay at the bottom"
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-hidden-text-hidden t
|
|
"Wether weechat.el should hide or show hidden text. "
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-connect-hook nil
|
|
"Hook run when successfully connected and authenticated."
|
|
:type '(choice (const :tag "Off" nil)
|
|
(function :tag "Hook"))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-reconnect-buffers t
|
|
"Automatically re-monitor channel buffers which were opened on a prior connection."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-reconnect-retries 5
|
|
"Number of max. retries when reconnecting"
|
|
:type '(choice (integer :tag "Number of retries")
|
|
(const :tag "No auto-reconnect" nil))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-monitor-buffers ()
|
|
"List of buffer names to auto-monitor on connect.
|
|
|
|
If value is a list, buffers corresponding the names will be
|
|
monitored on connect. If value is a string, monitor all buffers
|
|
matching the string as regexp. A value of t will monitor all
|
|
available buffers. Be warned, a too long list will use much
|
|
bandwidth on connect."
|
|
:type '(choice (const :tag "All" t)
|
|
(repeat :tag "List" string)
|
|
string)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-monitor-new-buffers 'silent
|
|
"Wether to auto-monitor new WeeChat buffers.
|
|
|
|
Value can be t, silent or nil. If t, new Emacs buffers will be
|
|
created when a new buffer in WeeChat is opened. If value
|
|
is (quote silent), new buffers will be opened in background. If
|
|
nil, no action will be taken for new WeeChat buffers."
|
|
:type '(choice (const :tag "Popup new buffer" t)
|
|
(const :tag "Open in background" 'silent)
|
|
(const :tag "Do nothing" nil))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-monitor-buffer-function 'message
|
|
"Function called when a new buffer is monitored. Useful to
|
|
display notifications.
|
|
|
|
Value is either 'message or a function taking one argument (a
|
|
buffer-ptr). "
|
|
:type '(choice (const :tag "Message in the mode line" 'message)
|
|
hook)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-auto-close-buffers nil
|
|
"Wether to auto-close closed WeeChat buffers."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-time-format "%H:%M:%S"
|
|
"How to format time stamps.
|
|
See `format-time-string' for format description."
|
|
:type 'string
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-text-column 22
|
|
"Column after which text will be inserted.
|
|
If `(length (concat nick timestamp))' is longer than this value,
|
|
text-column will be increased for that line."
|
|
:type 'integer
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-max-nick-length nil
|
|
"Maximum length of nicknames. Longer nicks will be truncated.
|
|
|
|
Note that this option will apply to all prefixes, not just
|
|
nicknames."
|
|
:type '(choice
|
|
(integer :tag "Max length")
|
|
(const :tag "Off" nil))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-fill-text t
|
|
"Wether weechat should fill text paragraphs automatically."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-fill-column 'frame-width
|
|
"Column used for filling text in buffers."
|
|
:type '(choice
|
|
(const :tag "Frame width" 'frame-width)
|
|
(integer :tag "Number")
|
|
(const :tag "Default" t))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-notification-mode :monitored
|
|
"When to notify the user.
|
|
|
|
Possible values are nil (Never), :monitored (Only monitored
|
|
buffers) and t (All buffers)."
|
|
:type '(choice
|
|
(const :tag "Never" nil)
|
|
(const :tag "Monitored buffers" :monitored)
|
|
(const :tag "All Buffers" t))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-notification-types '(:highlight :disconnect :query)
|
|
"Events for which a notification should be shown."
|
|
:type '(repeat symbol)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-header-line-format "%n on %c/%s: %t"
|
|
"Header line format.
|
|
Set to nil to disable header line. Supported options are:
|
|
|
|
- %n nick name
|
|
- %s server name
|
|
- %c channel name
|
|
- %N buffer name
|
|
- %t topic"
|
|
:type '(choice (const :tag "Disabled" nil)
|
|
string)
|
|
:set (lambda (sym val)
|
|
(set sym val)
|
|
(when (fboundp 'weechat-update-header-line)
|
|
(weechat-update-header-line)))
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-input-ring-size 20
|
|
"Size for the input ring."
|
|
:type 'integer
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-insert-modify-hook nil
|
|
"The hook will be called after new text is inserted into the buffer.
|
|
It is called with narrowing in the correct buffer."
|
|
:type 'hook
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-message-post-receive-functions nil
|
|
"List of function called after a new line was received for a buffer.
|
|
|
|
This hook is useful in conjunction with
|
|
`weechat-last-background-message-date' or
|
|
`weechat-last-background-highlight-date'.
|
|
|
|
Functions must take one argument: The buffer-ptr.
|
|
|
|
If the weechat-buffer is currently associated with an emacs
|
|
buffer, the functions will get called with the active buffer set
|
|
to it."
|
|
:type 'hook
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-message-filter-functions nil
|
|
"List of functions called in sequence before a line will be sent to the server.
|
|
|
|
The functions (a b c) are applied like (a (b (c input-string))).
|
|
If a function returns nil, evaluation will stop and the line is
|
|
ignored."
|
|
:type '(repeat :tag "List" function)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-message-filter-require-double-ret nil
|
|
"If t, pressing RET once will show the result of the filter.
|
|
|
|
If nil, filtering will take place transparently when sending the
|
|
message."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-complete-order-nickname t
|
|
"If non-nil nicknames are completed in order of most recent speaker."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-password-callback 'weechat-password-auth-source-callback
|
|
"Function called to get the relay password. Set to nil if no
|
|
password is needed.
|
|
|
|
Value must be a function with two arguments: Hostname and port.
|
|
The return value must be either a string, a function which
|
|
returns a string, or nil."
|
|
:type 'function
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-buffer-activity-types '(:irc/privmsg :irc/action :irc/notice)
|
|
"List of types which will contribute to buffer activity."
|
|
:type '(repeat :tag "List" symbol)
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-buffer-kill-buffers-on-disconnect nil
|
|
"Kill buffers if the connection is disconnected by the user."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-sync-active-buffer nil
|
|
"Sync currently visible buffer with the relay (one-way).
|
|
|
|
When set to t, weechat.el will switch the currently active
|
|
weechat buffer on the relay server when visiting a buffer in
|
|
weechat.
|
|
|
|
This is useful when setting irc.msgbuffer.* to 'current'.
|
|
|
|
Syncing is done when sending a command/message to the buffer."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-sync-buffer-read-status nil
|
|
"Mark buffers as read in the relay, when read with weechat.el.
|
|
|
|
When set to t, weechat will automatically mark buffers as read in
|
|
the relay, when they are visited in the client.
|
|
|
|
Be aware, that this setting can loose highlights: If the
|
|
highlight occurred more than `weechat-initial-lines' before Emacs
|
|
connects to the relay, reading the Emacs buffer will not show the
|
|
highlight, but mark the buffer as read.
|
|
|
|
Also, weechat >= 1.0 is required for this to work."
|
|
:type 'boolean
|
|
:group 'weechat)
|
|
|
|
(defcustom weechat-completing-read-function 'weechat--try-ido
|
|
"Function to prompt for channel names.
|
|
|
|
The function must comply to the interface of `completing-read'.
|
|
|
|
Possible choices would be `ido-completing-read' or
|
|
`completing-read'."
|
|
:type '(choice
|
|
(const :tag "Ido" weechat--try-ido)
|
|
(const :tag "Ivy" weechat--try-ivy)
|
|
(const :tag "Default" completing-read)
|
|
(function :tag "Other"))
|
|
:group 'weechat)
|
|
|
|
(defvar weechat--buffer-hashes (make-hash-table :test 'equal))
|
|
|
|
(defvar weechat--connected nil)
|
|
|
|
(defvar weechat-host-history nil
|
|
"List of recently connected hosts.")
|
|
(defvar weechat-last-port nil
|
|
"Last port connected to.")
|
|
(defvar weechat-mode-history nil
|
|
"List of recently used connection modes.")
|
|
|
|
(defvar weechat-version nil)
|
|
(add-to-list 'version-regexp-alist '("^[-_+ ]dev$" . -3))
|
|
|
|
(defvar weechat-buffer-opened-functions nil
|
|
"Hook ran when a WeeChat buffer opens.")
|
|
|
|
(defvar weechat-buffer-closed-functions nil
|
|
"Hook ran when a WeeChat buffer closes.")
|
|
|
|
(defvar weechat-notification-handler-functions nil
|
|
"List of functions called to display notificiations.
|
|
|
|
The functions are called with the following arguments:
|
|
|
|
TYPE, a symbol from `weechat-notification-types' Other optional
|
|
arguments are SENDER, TEXT, DATE, and BUFFER-PTR.")
|
|
|
|
(defvar weechat-inhibit-notifications nil
|
|
"Non-nil means don't display any weechat notifications.")
|
|
|
|
(defun weechat-load-modules-maybe ()
|
|
"Load all modules listed in `weechat-modules'"
|
|
;; Inspired by `org-load-modules-maybe'
|
|
(dolist (module weechat-modules)
|
|
(condition-case nil (load-library (symbol-name module))
|
|
(error (weechat-warn "Problems while trying to load feature `%s'" module)))))
|
|
|
|
;;; This is a hack to load modules after weechat.el is loaded
|
|
;;; completely
|
|
(eval-after-load 'weechat
|
|
'(weechat-load-modules-maybe))
|
|
|
|
;;; Add all hooks ending in -functions to
|
|
;;; `unload-feature-special-hooks' to make `unload-feature' remove the
|
|
;;; hooks on unload
|
|
|
|
(eval-after-load 'loadhist
|
|
'(setq unload-feature-special-hooks
|
|
(append unload-feature-special-hooks
|
|
'(weechat-buffer-opened-functions
|
|
weechat-buffer-closed-functions
|
|
weechat-notification-handler-functions
|
|
weechat-message-post-receive-functions))))
|
|
|
|
(defun weechat-connected-p ()
|
|
(and (weechat-relay-connected-p)
|
|
weechat--connected))
|
|
|
|
(defun weechat-buffer-hash (buffer-ptr)
|
|
(gethash buffer-ptr weechat--buffer-hashes))
|
|
|
|
(defun weechat--clear-buffer-store ()
|
|
(clrhash weechat--buffer-hashes))
|
|
|
|
(defun weechat--store-buffer-hash (ptr alist &optional replace)
|
|
(when (and (not replace) (weechat-buffer-hash ptr))
|
|
(error "Buffer '%s' already exists" ptr))
|
|
(let ((hash (make-hash-table :test 'equal)))
|
|
(dolist (x alist)
|
|
(puthash (car x) (cdr x) hash))
|
|
(puthash ptr hash weechat--buffer-hashes)))
|
|
|
|
(defun weechat--remove-buffer-hash (ptr)
|
|
(unless (weechat-buffer-hash ptr)
|
|
(error "Buffer '%s' doesn't exist" ptr))
|
|
(remhash ptr weechat--buffer-hashes))
|
|
|
|
(defun weechat--handle-buffer-list (response)
|
|
;; Remove all hashes not found in the new list
|
|
(let* ((hdata (car response))
|
|
(buffer-pointers (mapcar (lambda (x) (car (weechat--hdata-value-pointer-path x)))
|
|
(weechat--hdata-values hdata))))
|
|
(maphash (lambda (k _)
|
|
(unless (cl-find k buffer-pointers
|
|
:test 'equal)
|
|
(remhash k weechat--buffer-hashes)))
|
|
(copy-hash-table weechat--buffer-hashes))
|
|
;; Update all remaining values
|
|
(dolist (value (weechat--hdata-values hdata))
|
|
(let* ((buffer-ptr (car (weechat--hdata-value-pointer-path value)))
|
|
(buffer-hash (weechat-buffer-hash buffer-ptr))
|
|
(alist (weechat--hdata-value-alist value)))
|
|
(if (hash-table-p buffer-hash)
|
|
(dolist (v alist)
|
|
(puthash (car v) (cdr v) buffer-hash))
|
|
(weechat--store-buffer-hash buffer-ptr alist))))))
|
|
|
|
(defun weechat-update-buffer-list (&optional callback)
|
|
(weechat-relay-send-command
|
|
"hdata buffer:gui_buffers(*) number,name,short_name,title,local_variables"
|
|
(lambda (response)
|
|
(weechat--handle-buffer-list response)
|
|
(when (functionp callback)
|
|
(funcall callback)))))
|
|
|
|
(defun weechat--handle-buffer-opened (response)
|
|
(let* ((hdata (car response))
|
|
(value (car (weechat--hdata-values hdata)))
|
|
(buffer-ptr (car (weechat--hdata-value-pointer-path value))))
|
|
(when (weechat-buffer-hash buffer-ptr)
|
|
(error "Received '_buffer_opened' event for '%s' but the buffer exists already" buffer-ptr))
|
|
(weechat--store-buffer-hash buffer-ptr (weechat--hdata-value-alist value))
|
|
|
|
(when weechat-auto-monitor-new-buffers
|
|
(weechat-monitor-buffer
|
|
buffer-ptr
|
|
(not (eq weechat-auto-monitor-new-buffers 'silent))))
|
|
|
|
(run-hook-with-args 'weechat-buffer-opened-functions
|
|
buffer-ptr)))
|
|
|
|
(defun weechat--handle-buffer-closed (response)
|
|
(let* ((hdata (car response))
|
|
(value (car (weechat--hdata-values hdata)))
|
|
(buffer-ptr (car (weechat--hdata-value-pointer-path value)))
|
|
(emacs-buffer (weechat--emacs-buffer buffer-ptr)))
|
|
(unless (weechat-buffer-hash buffer-ptr)
|
|
(error "Received '_buffer_closed' event for '%s' but the buffer doesn't exist" buffer-ptr))
|
|
(when (buffer-live-p emacs-buffer)
|
|
;; Add text about quitting etc. bla
|
|
(weechat-print-line buffer-ptr :prefix "Buffer closed")
|
|
;; Close buffer if user wants this
|
|
(when weechat-auto-close-buffers
|
|
(kill-buffer emacs-buffer)))
|
|
;; Remove from buffer hash map
|
|
(weechat--remove-buffer-hash buffer-ptr)
|
|
;; Finally, run hook
|
|
(run-hook-with-args 'weechat-buffer-closed-functions
|
|
buffer-ptr)))
|
|
|
|
(defmacro weechat->> (&rest body)
|
|
(let ((result (pop body)))
|
|
(dolist (form body result)
|
|
(setq result (append form (list result))))))
|
|
|
|
(defmacro weechat-> (&rest body)
|
|
(let ((result (pop body)))
|
|
(dolist (form body result)
|
|
(setq result (append (list (car form) result)
|
|
(cdr form))))))
|
|
|
|
(defun weechat--handle-buffer-renamed (response)
|
|
(let* ((hdata (car response))
|
|
(value (car (weechat--hdata-values hdata)))
|
|
(buffer-ptr (car (weechat--hdata-value-pointer-path value)))
|
|
(hash (weechat-buffer-hash buffer-ptr)))
|
|
(unless hash
|
|
(error "Received '_buffer_renamed' event for '%s' but the buffer doesn't exist" buffer-ptr))
|
|
(puthash "number" (assoc-default "number" value) hash)
|
|
(puthash "full_name" (assoc-default "full_name" value) hash)
|
|
(puthash "short_name" (assoc-default "short_name" value) hash)
|
|
(puthash "local_variables" (assoc-default "local_variables" value) hash)))
|
|
|
|
(weechat-relay-add-id-callback "_buffer_opened" #'weechat--handle-buffer-opened nil 'force)
|
|
(weechat-relay-add-id-callback "_buffer_closing" #'weechat--handle-buffer-closed nil 'force)
|
|
(weechat-relay-add-id-callback "_buffer_renamed" #'weechat--handle-buffer-renamed nil 'force)
|
|
|
|
;;; Handle pong replies
|
|
(weechat-relay-add-id-callback "_buffer_renamed" #'weechat--handle-buffer-renamed nil 'force)
|
|
|
|
(defvar weechat-topic nil
|
|
"Topic of the channel buffer.")
|
|
|
|
(defun weechat--handle-buffer-title-changed (response)
|
|
(let* ((hdata (car response))
|
|
(value (car (weechat--hdata-values hdata)))
|
|
(buffer-ptr (car (weechat--hdata-value-pointer-path value)))
|
|
(hash (weechat-buffer-hash buffer-ptr))
|
|
(alist (weechat--hdata-value-alist value))
|
|
(buffer (gethash :emacs/buffer hash))
|
|
(new-title (or (cdr (assoc-string "title" alist)) "")))
|
|
(unless (weechat-buffer-hash buffer-ptr)
|
|
(error "Received '_buffer_title_changed' event for '%s' but the buffer doesn't exist" buffer-ptr))
|
|
(puthash "title" new-title hash)
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(setq weechat-topic new-title)
|
|
(weechat-update-header-line-buffer buffer)))))
|
|
|
|
(weechat-relay-add-id-callback "_buffer_title_changed" #'weechat--handle-buffer-title-changed nil 'force)
|
|
|
|
(defun weechat-merge-alists (old new)
|
|
(dolist (k new old)
|
|
(let ((to-remove (assoc-string (car k) old)))
|
|
(setq old (cons k (remove to-remove old))))))
|
|
|
|
(defun weechat--handle-localvar-changed (response)
|
|
(let* ((hdata (car response))
|
|
(value (car (weechat--hdata-values hdata)))
|
|
(buffer-ptr (car (weechat--hdata-value-pointer-path value)))
|
|
(hash (weechat-buffer-hash buffer-ptr))
|
|
(alist (weechat--hdata-value-alist value))
|
|
(buffer (gethash :emacs/buffer hash))
|
|
(old-local-variables (gethash "local_variables" hash))
|
|
(new-local-variables (cdr (assoc-string "local_variables" alist))))
|
|
(unless (weechat-buffer-hash buffer-ptr)
|
|
(error "Received '_buffer_localvar_changed' event for '%s' but the buffer doesn't exist"
|
|
buffer-ptr))
|
|
(puthash "local_variables"
|
|
(weechat-merge-alists old-local-variables new-local-variables)
|
|
hash)
|
|
(when buffer
|
|
(with-current-buffer buffer
|
|
(weechat-update-prompt)
|
|
(weechat-update-header-line-buffer buffer)))))
|
|
|
|
(weechat-relay-add-id-callback "_buffer_localvar_changed" #'weechat--handle-localvar-changed nil 'force)
|
|
(weechat-relay-add-id-callback "_buffer_localvar_added" #'weechat--handle-localvar-changed nil 'force)
|
|
|
|
(defun weechat-password-auth-source-callback (host port)
|
|
"Get password for HOST and PORT via `auth-source-search'.
|
|
Returns either a string or a function. See Info node `(auth) Top' for details."
|
|
(when (fboundp 'auth-source-search)
|
|
(weechat-message "Using auth-source to retrieve weechat relay password")
|
|
(plist-get
|
|
(car (auth-source-search
|
|
:max 1
|
|
:host host
|
|
:port port
|
|
:require '(:secret)))
|
|
:secret)))
|
|
|
|
(defun weechat-get-password (host port)
|
|
"Get password for HOST and PORT.
|
|
Return either a string, a function returning a string, or nil."
|
|
(when (functionp weechat-password-callback)
|
|
(funcall weechat-password-callback host port)))
|
|
|
|
(defvar weechat-mode-completion-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(set-keymap-parent map minibuffer-local-map)
|
|
(define-key map "\t" 'minibuffer-complete)
|
|
(define-key map "?" 'minibuffer-completion-help)
|
|
map)
|
|
"Weechat mode selection: Local keymap for minibuffer input with completion.")
|
|
|
|
(defvar weechat-reconnect-timer nil)
|
|
(defun weechat-cancel-reconnect ()
|
|
(when (timerp weechat-reconnect-timer)
|
|
(cancel-timer weechat-reconnect-timer)
|
|
(setq weechat-reconnect-timer nil))
|
|
(unintern 'weechat-auto-reconnect-retries-left obarray))
|
|
|
|
;;;###autoload
|
|
(defun weechat-connect (&optional host port password mode force-disconnect)
|
|
"Connect to WeeChat.
|
|
|
|
HOST is the relay host, `weechat-host-default' by default.
|
|
PORT is the port where the relay listens, `weechat-port-default' by default.
|
|
PASSWORD is either a string, a function or nil.
|
|
MODE is null or 'plain for a plain socket, t or 'ssl for a TLS socket;
|
|
a string denotes a command to run. You can use %h and %p to interpolate host
|
|
and port number respectively."
|
|
(interactive
|
|
(let* ((host
|
|
(read-string
|
|
(format "Relay host (default '%s'): " weechat-host-default)
|
|
nil 'weechat-host-history weechat-host-default))
|
|
(port
|
|
(read-number "Port: " (or weechat-last-port weechat-port-default)))
|
|
(mode (let*
|
|
((minibuffer-local-completion-map weechat-mode-completion-map)
|
|
(modestr (completing-read
|
|
(format "Mode (`plain', `ssl' or command, default `%s'): "
|
|
weechat-mode-default)
|
|
'("plain" "ssl" "ssh -W localhost:%p %h")
|
|
nil nil nil 'weechat-mode-history
|
|
;; NOTE: `completing-read' is fine when
|
|
;; passed a symbol, but helm breaks.
|
|
;; The following ensures we always pass
|
|
;; a string.
|
|
(format "%s" weechat-mode-default))))
|
|
(cond
|
|
((string= modestr "") nil)
|
|
((string= modestr "plain") 'plain)
|
|
((string= modestr "ssl") 'ssl)
|
|
(t modestr)))))
|
|
(setq weechat-last-port port)
|
|
(list
|
|
host port
|
|
(or
|
|
(progn
|
|
(weechat-message "Trying to get password via `weechat-password-callback'...")
|
|
(weechat-get-password host port))
|
|
;; Use lexical-let to scramble password lambda in *Backtrace*
|
|
(read-passwd "Password: "))
|
|
mode
|
|
nil)))
|
|
;; Cancel the reconnect timer to prevent surprises
|
|
(weechat-cancel-reconnect)
|
|
;; Handle when the user is already connected etc.
|
|
(let* ((host (or host weechat-host-default))
|
|
(port (or port weechat-port-default))
|
|
(password (or password
|
|
(weechat-get-password host port)))
|
|
(mode (or mode weechat-mode-default)))
|
|
(weechat-message "Weechat connecting to %s:%d" host port)
|
|
(when (weechat-relay-connected-p)
|
|
(if (or force-disconnect
|
|
(y-or-n-p "Already connected. Disconnect other connection? "))
|
|
(weechat-relay-disconnect)
|
|
(error "Can't open two connections")))
|
|
(when (and (stringp host)
|
|
(integerp port))
|
|
(weechat-relay-connect
|
|
host
|
|
port
|
|
mode
|
|
(lambda ()
|
|
(weechat-relay-authenticate password)
|
|
(weechat-relay-send-command
|
|
"info version"
|
|
(lambda (data)
|
|
(let ((version-str (cdar data)))
|
|
(weechat-message "Connected to '%s', version %s" host
|
|
version-str)
|
|
(setq weechat-version version-str))
|
|
(weechat-update-buffer-list
|
|
(lambda ()
|
|
(weechat-relay-send-command "sync")
|
|
(setq weechat--connected t)
|
|
(weechat--relay-start-ping-timer)
|
|
(weechat-cancel-reconnect)
|
|
(run-hooks 'weechat-connect-hook))))))))))
|
|
|
|
(defvar weechat-auto-reconnect-retries-left)
|
|
(defun weechat-handle-reconnect-maybe ()
|
|
(weechat-cancel-reconnect)
|
|
(unless (boundp 'weechat-auto-reconnect-retries-left)
|
|
(setq weechat-auto-reconnect-retries-left
|
|
weechat-auto-reconnect-retries))
|
|
(when (> weechat-auto-reconnect-retries-left 0)
|
|
(let ((host (car weechat-host-history))
|
|
(port weechat-last-port)
|
|
(delay (lsh 1 (- weechat-auto-reconnect-retries
|
|
weechat-auto-reconnect-retries-left))))
|
|
(if (not (weechat-get-password host port))
|
|
(weechat-message "Not reconnecting: No password stored.")
|
|
(weechat-message "Reconnecting in %ds..." delay)
|
|
(cl-decf weechat-auto-reconnect-retries-left)
|
|
(setq weechat-reconnect-timer
|
|
(run-with-timer
|
|
delay nil
|
|
(lambda ()
|
|
(weechat-connect
|
|
host
|
|
port
|
|
(weechat-get-password host port)
|
|
(car weechat-mode-history)
|
|
'force-disconnect)))))
|
|
t)))
|
|
|
|
(defun weechat-handle-disconnect ()
|
|
(setq weechat--connected nil
|
|
weechat-version nil)
|
|
(unless (and weechat-auto-reconnect-retries
|
|
(weechat-handle-reconnect-maybe))
|
|
;; Print 'disconnected' message to all channel buffers
|
|
(maphash (lambda (k v)
|
|
(when (bufferp (gethash :emacs/buffer v))
|
|
(with-current-buffer (gethash :emacs/buffer v)
|
|
(weechat-print-line k
|
|
:prefix "!!!"
|
|
:text "Lost connection to relay server"
|
|
:date (current-time)
|
|
:line-type :irc/x-error))))
|
|
weechat--buffer-hashes)
|
|
(weechat-notify :disconnect
|
|
:date (current-time))))
|
|
|
|
(defun weechat-disconnect ()
|
|
(interactive)
|
|
;; It's safe to lexical-bind the retry limit to nil to disable
|
|
;; reconnects
|
|
(let ((weechat-auto-reconnect-retries nil))
|
|
;; Disconnect the relay. `weechat-relay-disconnect-hook' will NOT
|
|
;; run.
|
|
(weechat-relay-disconnect)
|
|
(weechat-handle-disconnect)
|
|
(when weechat-buffer-kill-buffers-on-disconnect
|
|
(weechat-do-buffers (kill-buffer)))
|
|
(clrhash weechat--buffer-hashes)
|
|
(setq weechat--connected nil)))
|
|
|
|
(add-hook 'weechat-relay-disconnect-hook 'weechat-handle-disconnect)
|
|
|
|
(defun weechat-buffer-name (buffer-ptr)
|
|
(let ((hash (weechat-buffer-hash buffer-ptr)))
|
|
(or (gethash "name" hash)
|
|
(gethash "full_name" hash)
|
|
;; NOTE: Short name isn't useful to identify the buffer
|
|
;; (gethash "short_name" hash)
|
|
)))
|
|
|
|
(defun weechat--find-buffer (name)
|
|
"Return buffer-ptr for channel NAME."
|
|
(let (ret)
|
|
(maphash
|
|
(lambda (ptr _)
|
|
(when (string= name (weechat-buffer-name ptr))
|
|
(setq ret ptr)))
|
|
weechat--buffer-hashes)
|
|
ret))
|
|
|
|
(defun weechat--channel-names-pred (l r)
|
|
"Compare channel name L and R.
|
|
Return non-nil if L < R. Names of actual channels should come first."
|
|
(let ((l? (s-contains? "#" l))
|
|
(r? (s-contains? "#" r))
|
|
(l<r? (string< l r)))
|
|
(or (and l? l<r?)
|
|
(and l? (not r?))
|
|
(and (not r?) l<r?))))
|
|
|
|
(defun weechat-channel-names (&optional arg sort)
|
|
"Return all available buffer names in WeeChat.
|
|
|
|
If ARG is non-nil, only return monitored buffers. If SORT is non-nil then sort
|
|
the channel list with actual channels coming first."
|
|
(let (ret)
|
|
(maphash
|
|
(lambda (k v)
|
|
(when (or (not arg) (buffer-live-p (gethash :emacs/buffer v)))
|
|
(setq ret (cons (weechat-buffer-name k) ret))))
|
|
weechat--buffer-hashes)
|
|
(if sort
|
|
(sort ret #'weechat--channel-names-pred)
|
|
ret)))
|
|
|
|
(defun weechat-buffer-list ()
|
|
"List all Weechat buffers."
|
|
(let (acc)
|
|
(maphash (lambda (_ v)
|
|
(when (buffer-live-p (gethash :emacs/buffer v))
|
|
(setq acc (cons (gethash :emacs/buffer v) acc))))
|
|
weechat--buffer-hashes)
|
|
acc))
|
|
|
|
(defmacro weechat-do-buffers (&rest body)
|
|
"Evaluate body in each WeeChat buffer."
|
|
`(maphash (lambda (_ v)
|
|
(let ((buffer (gethash :emacs/buffer v)))
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
,@body))))
|
|
weechat--buffer-hashes))
|
|
|
|
(defun weechat-channel-names-unmonitored ()
|
|
(cl-remove-if
|
|
(lambda (name)
|
|
(weechat--emacs-buffer
|
|
(weechat--find-buffer name)))
|
|
(weechat-channel-names)))
|
|
|
|
(defun weechat--emacs-buffer (buffer-ptr)
|
|
(let ((hash (gethash buffer-ptr weechat--buffer-hashes)))
|
|
(gethash :emacs/buffer hash)))
|
|
|
|
(defun weechat-visible-buffers (&optional current-frame-only)
|
|
"Returns list of all visible weechat.el channel buffers.
|
|
|
|
Optional argument CURRENT-FRAME-ONLY limits list to current
|
|
frame."
|
|
(let (ret)
|
|
(weechat-do-buffers
|
|
(when (window-live-p (get-buffer-window
|
|
(current-buffer)
|
|
(not current-frame-only)))
|
|
(setq ret (cons (current-buffer) ret))))
|
|
ret))
|
|
|
|
;;; Buffer local variables
|
|
(defvar weechat-buffer-ptr)
|
|
(defvar weechat-server-buffer)
|
|
(defvar weechat-buffer-number)
|
|
(defvar weechat-local-prompt)
|
|
(defvar weechat-lines-received)
|
|
|
|
;;; The following functions handle buffer-hash-entries storing the
|
|
;;; last highlight and the last message. The entries will be cleared
|
|
;;; automatically when the buffer becomes visible. This is useful for
|
|
;;; a mode-line display of modified buffers etc.
|
|
|
|
(defvar weechat-buffer-background-message-hook nil
|
|
"Hook called when a message was received in a weechat buffer
|
|
which isn't currently visible. Called with the corresponding
|
|
buffer active.")
|
|
(defvar weechat-buffer-background-highlight-hook nil
|
|
"Same as `weechat-buffer-background-message-hook', only for highlights.")
|
|
(defvar weechat-buffer-visited-hook nil
|
|
"Hook called when a weechat-buffer is visited and the
|
|
background-data is reset.")
|
|
|
|
(defun weechat--reset-relay-read-status (buffer-ptr)
|
|
"Mark the buffer BUFFER-PTR as read in the relay."
|
|
(when (and weechat-version
|
|
(not (version< weechat-version "1.0"))
|
|
weechat-sync-buffer-read-status)
|
|
(weechat-relay-send-command (concat "input " buffer-ptr " /buffer set hotlist -1"))
|
|
(weechat-relay-send-command (concat "input " buffer-ptr " /input set_unread_current_buffer"))))
|
|
|
|
(defun weechat-reset-buffer-modified (buffer-ptr)
|
|
(let ((hash (weechat-buffer-hash buffer-ptr)))
|
|
(when (hash-table-p hash)
|
|
(weechat--reset-relay-read-status buffer-ptr)
|
|
(run-hooks 'weechat-buffer-visited-hook)
|
|
(remhash :background-message hash)
|
|
(remhash :background-highlight hash))))
|
|
|
|
(defun weechat-buffer-modified-update-hash (hash line-data)
|
|
(let ((hash (or hash (make-hash-table))))
|
|
(puthash :date (assoc-default "date" line-data) hash)
|
|
(puthash :sender (weechat--get-nick-from-line-data line-data) hash)
|
|
(puthash :count (1+ (gethash :count hash 0)) hash)
|
|
hash))
|
|
|
|
(defun weechat-update-buffer-modified (buffer-ptr line-data)
|
|
(let ((line-type (weechat-line-type line-data))
|
|
(line-date (assoc-default "date" line-data))
|
|
(nick (weechat--get-nick-from-line-data line-data))
|
|
(hash (weechat-buffer-hash buffer-ptr))
|
|
(emacs-buffer (weechat--emacs-buffer buffer-ptr)))
|
|
(unless (hash-table-p hash)
|
|
(error "Tried to update modification date for unknown buffer-ptr '%s'" buffer-ptr))
|
|
(if (and (buffer-live-p emacs-buffer)
|
|
(cl-find emacs-buffer (weechat-visible-buffers) :test 'equal))
|
|
;; Buffer is visible. Reset modification
|
|
(weechat-reset-buffer-modified buffer-ptr)
|
|
;; Buffer invisible. Store modifications.
|
|
(when (and line-type line-date nick)
|
|
(cond
|
|
;; Message from ourself. Reset.
|
|
((string= nick (weechat-get-local-var "nick" buffer-ptr))
|
|
(weechat-reset-buffer-modified buffer-ptr))
|
|
;; General activity
|
|
((memq line-type weechat-buffer-activity-types)
|
|
(puthash :background-message
|
|
(weechat-buffer-modified-update-hash
|
|
(gethash :background-message hash)
|
|
line-data)
|
|
hash)
|
|
(when (buffer-live-p emacs-buffer)
|
|
(with-current-buffer emacs-buffer
|
|
(run-hooks 'weechat-buffer-background-message-hook)))))
|
|
;; Highlight
|
|
(when (eq 1 (cdr (assoc-string "highlight" line-data)))
|
|
(puthash :background-highlight
|
|
(weechat-buffer-modified-update-hash
|
|
(gethash :background-highlight hash)
|
|
line-data)
|
|
hash)
|
|
(when (buffer-live-p emacs-buffer)
|
|
(with-current-buffer emacs-buffer
|
|
(run-hooks 'weechat-buffer-background-highlight-hook))))))))
|
|
|
|
(defun weechat-window-configuration-change ()
|
|
"Resets modification dates for all visible buffers."
|
|
(dolist (b (weechat-visible-buffers))
|
|
(with-current-buffer b
|
|
(weechat-reset-buffer-modified weechat-buffer-ptr))))
|
|
|
|
(add-hook 'window-configuration-change-hook 'weechat-window-configuration-change)
|
|
|
|
;;; Borrowed this behavior from rcirc
|
|
(defvar weechat-prompt-start-marker)
|
|
(defvar weechat-prompt-end-marker)
|
|
|
|
(defun weechat-update-prompt ()
|
|
(save-excursion
|
|
(let ((start (marker-position weechat-prompt-start-marker))
|
|
(inhibit-read-only t))
|
|
(setq weechat-local-prompt
|
|
(format-spec weechat-prompt
|
|
(format-spec-make ?n (weechat-get-local-var "nick"))))
|
|
(delete-region weechat-prompt-start-marker weechat-prompt-end-marker)
|
|
(goto-char weechat-prompt-end-marker)
|
|
(insert-before-markers weechat-local-prompt)
|
|
(set-marker weechat-prompt-start-marker start)
|
|
(unless (zerop (- weechat-prompt-end-marker
|
|
weechat-prompt-start-marker))
|
|
(add-text-properties weechat-prompt-start-marker
|
|
weechat-prompt-end-marker
|
|
(list 'face 'weechat-prompt-face
|
|
'read-only t
|
|
'field t
|
|
'rear-nonsticky t
|
|
'front-sticky t))))))
|
|
|
|
(cl-defun weechat-notify (type &key sender text date buffer-ptr)
|
|
(when (and (memq type weechat-notification-types)
|
|
(or (eq weechat-notification-mode t)
|
|
(and (eql weechat-notification-mode :monitored)
|
|
(local-variable-p 'weechat-buffer-ptr)
|
|
(buffer-live-p (weechat--emacs-buffer weechat-buffer-ptr)))))
|
|
(dolist (fn weechat-notification-handler-functions)
|
|
(with-demoted-errors
|
|
(funcall fn type sender text date buffer-ptr)))))
|
|
|
|
(defun weechat-buffer-p (&optional buffer)
|
|
"Return non-nil if buffer is a WeeChat buffer."
|
|
(eq 'weechat-mode (buffer-local-value 'major-mode
|
|
(or buffer (current-buffer)))))
|
|
|
|
(defun weechat-narrow-to-line ()
|
|
(interactive)
|
|
(unless (weechat-buffer-p)
|
|
(error "No weechat-mode buffer"))
|
|
(when (> (point) weechat-prompt-start-marker)
|
|
(error "Only narrowing to lines is supported"))
|
|
(narrow-to-region (point-at-bol) (min (point-at-eol)
|
|
weechat-prompt-start-marker)))
|
|
|
|
(defun weechat-truncate-buffer ()
|
|
(when (integerp weechat-buffer-line-limit)
|
|
(save-excursion
|
|
(save-restriction
|
|
(widen)
|
|
(let ((lines-to-delete (- (- weechat-buffer-line-limit
|
|
(count-lines (point-min) (point-max)))))
|
|
(inhibit-read-only t))
|
|
(when (> lines-to-delete 0)
|
|
(goto-char (point-min))
|
|
(forward-line lines-to-delete)
|
|
(delete-region (point-min) (point))))))))
|
|
|
|
(defun weechat-line-add-properties (nick date highlight invisible)
|
|
"Add various text properties (read-only, etc.) to a line.
|
|
|
|
Must be called with `weechat-narrow-to-line' active."
|
|
;; Add `date' and `highlighted' to the whole line
|
|
(add-text-properties (point-min) (point-max)
|
|
(list 'weechat-nick nick
|
|
'weechat-date date
|
|
'weechat-highlighted highlight))
|
|
|
|
;; Make line read-only if `weechat-read-only' is t
|
|
(when weechat-read-only
|
|
(add-text-properties (point-min) (point-max)
|
|
'(read-only t))))
|
|
|
|
(defun weechat-recenter-bottom-maybe (&optional window force)
|
|
(when weechat-auto-recenter
|
|
(let ((window (or (windowp window) (get-buffer-window))))
|
|
(when window
|
|
(with-selected-window window
|
|
(when (weechat-buffer-p)
|
|
(when (or force
|
|
(<= (- (window-body-height)
|
|
(count-screen-lines (window-point)
|
|
(window-start))
|
|
2) ;2, not 1 (like in rcirc)
|
|
;because of the header-line
|
|
0))
|
|
(recenter -1))))))))
|
|
|
|
(defun weechat-line-date ()
|
|
"Return the date of the line under point."
|
|
(get-text-property (point) 'weechat-date))
|
|
|
|
(defun weechat-line-nick ()
|
|
"Return the nickname of the line under point."
|
|
(get-text-property (point) 'weechat-nick))
|
|
|
|
(defun weechat-line-text-start ()
|
|
"Return position where line text (message etc.) starts.
|
|
|
|
Might return the value of (point-at-eol) when there's no
|
|
text (technically, this shouldn't happen)."
|
|
(next-single-property-change (point-at-bol)
|
|
'weechat-text
|
|
nil
|
|
(point-at-eol)))
|
|
|
|
(defun weechat-line-text ()
|
|
(save-excursion
|
|
(let ((start (weechat-line-text-start)))
|
|
(when (< start (point-at-eol))
|
|
(buffer-substring start (point-at-eol))))))
|
|
|
|
(cl-defun weechat-print-line (buffer-ptr &key prefix text date line-type highlight invisible nick)
|
|
(setq text (or text ""))
|
|
(setq prefix (or prefix ""))
|
|
(let ((buffer (weechat--emacs-buffer buffer-ptr)))
|
|
(unless (bufferp buffer)
|
|
(error "Couldn't find Emacs buffer for weechat-buffer %s" buffer-ptr))
|
|
(with-current-buffer buffer
|
|
(let ((at-end (= (point) weechat-prompt-end-marker))
|
|
(old-point (point-marker)))
|
|
(let ((inhibit-read-only t))
|
|
(goto-char (marker-position weechat-prompt-start-marker))
|
|
|
|
(save-restriction
|
|
;; Hack borrowed from rcirc:
|
|
;; temporarily set the marker insertion-type because
|
|
;; insert-before-markers results in hidden text in new buffers
|
|
(set-marker-insertion-type weechat-prompt-start-marker t)
|
|
(set-marker-insertion-type weechat-prompt-end-marker t)
|
|
|
|
(weechat-narrow-to-line)
|
|
|
|
(when date
|
|
(insert (propertize
|
|
(format-time-string weechat-time-format date)
|
|
'face 'weechat-time-face)
|
|
" "))
|
|
|
|
(unless (s-blank? (weechat-handle-color-codes prefix))
|
|
(let ((colorized-prefix (weechat-handle-color-codes prefix)))
|
|
(insert (if (and (integerp weechat-max-nick-length)
|
|
(> weechat-max-nick-length 0)
|
|
(> (length colorized-prefix) weechat-max-nick-length))
|
|
(store-substring
|
|
(substring colorized-prefix 0 weechat-max-nick-length)
|
|
(1- weechat-max-nick-length)
|
|
?\x2026)
|
|
colorized-prefix)))
|
|
(when (or (eq line-type :irc/privmsg)
|
|
(not line-type))
|
|
(insert ":")))
|
|
|
|
(let ((chars-to-insert
|
|
(- weechat-text-column
|
|
(- (point-max) (point-min)))))
|
|
(when (> chars-to-insert 0)
|
|
(insert-char ?\s chars-to-insert)))
|
|
|
|
;; Calculate `prefix-string' for nice `auto-fill' (using
|
|
;; overlays)
|
|
(let ((prefix-string (make-string (- (point-max) (point-min)) ?\s))
|
|
(text-start (point)))
|
|
;; trim & handle color codes
|
|
(let* ((text (weechat-> text
|
|
(s-trim)
|
|
(weechat-handle-color-codes)
|
|
(propertize 'weechat-text t))))
|
|
(insert (cond
|
|
(highlight
|
|
(propertize text 'face 'weechat-highlight-face))
|
|
((eq line-type :irc/x-error)
|
|
(propertize text 'face 'weechat-error-face))
|
|
(t text))
|
|
"\n"))
|
|
|
|
(when weechat-fill-text
|
|
;; Filling is slightly misleading here. We use this
|
|
;; awesome text property called `wrap-prefix'.
|
|
(let ((overlay (make-overlay text-start (point-max))))
|
|
(overlay-put overlay 'wrap-prefix
|
|
(propertize prefix-string 'face 'default)))))
|
|
|
|
;; Go to start of inserted line
|
|
(goto-char (1- (point))) ;skip newline
|
|
(goto-char (point-at-bol))
|
|
|
|
;; Add general properties
|
|
(weechat-line-add-properties nick date highlight invisible)
|
|
|
|
;; Important: Run the hook after everything else
|
|
(save-restriction
|
|
(run-hooks 'weechat-insert-modify-hook))))
|
|
|
|
;; Restore old position
|
|
(let ((p-to-go (if at-end weechat-prompt-end-marker old-point))
|
|
(w (get-buffer-window buffer)))
|
|
;; ...for non-active buffers (in windows)
|
|
(when (and (not (eq (selected-window) w))
|
|
(eq (current-buffer)
|
|
(window-buffer w)))
|
|
(set-window-point w p-to-go))
|
|
|
|
;; ...for active buffer
|
|
(goto-char p-to-go))
|
|
|
|
;; Recenter window if there are more lines than fit in the
|
|
;; frame. This is borrowed from rcirc.
|
|
(weechat-recenter-bottom-maybe)
|
|
|
|
(set-marker-insertion-type weechat-prompt-start-marker nil)
|
|
(set-marker-insertion-type weechat-prompt-end-marker nil))
|
|
|
|
;; Truncate
|
|
(weechat-truncate-buffer)
|
|
|
|
;; Drop undo information (borrowed from weechat)
|
|
(when (not (s-blank? (weechat-get-input)))
|
|
(buffer-disable-undo)
|
|
(buffer-enable-undo)))))
|
|
|
|
(defun weechat-line-type (line-hdata)
|
|
(let ((tags (cdr (assoc-string "tags_array" line-hdata))))
|
|
(cond
|
|
((member "irc_join" tags) :irc/join)
|
|
((member "irc_action" tags) :irc/action)
|
|
((member "irc_part" tags) :irc/part)
|
|
((member "irc_quit" tags) :irc/quit)
|
|
((member "irc_mode" tags) :irc/mode)
|
|
((member "irc_nick" tags) :irc/nick)
|
|
((member "irc_topic" tags) :irc/topic)
|
|
((member "irc_numeric" tags) :irc/numeric)
|
|
((member "irc_notice" tags) :irc/notice)
|
|
((member "irc_privmsg" tags) :irc/privmsg)
|
|
(:irc/unknown)))) ;fallback
|
|
|
|
(defun weechat-buffer-type (&optional buffer-ptr)
|
|
(let* ((buffer-ptr (or buffer-ptr weechat-buffer-ptr))
|
|
(type (weechat->> buffer-ptr
|
|
(weechat-buffer-hash)
|
|
(gethash "local_variables")
|
|
(assoc-string "type")
|
|
(cdr)) ))
|
|
(when (stringp type)
|
|
(intern (format ":%s" type)))))
|
|
|
|
(defvar weechat-user-list)
|
|
(defun weechat--user-list-add (nick)
|
|
(unless (s-blank? nick)
|
|
(setq weechat-user-list (cons nick (delete nick weechat-user-list)))))
|
|
(defun weechat--user-list-remove (nick)
|
|
(setq weechat-user-list (delete nick weechat-user-list)))
|
|
|
|
(defun weechat--get-nick-from-tag (line-hdata &optional nick-tag)
|
|
"Get nick name from tags_array in LINE-HDATA.
|
|
If NICK-TAG is nil then \"nick_\" as prefix else use NICK-TAG."
|
|
(setq nick-tag (or nick-tag "nick_"))
|
|
(let ((tags-array (cdr (assoc-string "tags_array" line-hdata))))
|
|
(when tags-array
|
|
(s-chop-prefix
|
|
nick-tag
|
|
(cl-find-if (lambda (s) (s-prefix? nick-tag s)) tags-array)))))
|
|
|
|
(defun weechat--get-nick-from-line-data (line-hdata)
|
|
"Get nick name from LINE-HDATA."
|
|
(or
|
|
(weechat--get-nick-from-tag line-hdata)
|
|
(let* ((prefix (cdr (assoc-string "prefix" line-hdata)))
|
|
;; Try to strip the color and prefix from nick
|
|
(nick-match (s-match "\x19\F[[:digit:]][[:digit:]]\\([^\x19]+\\)$" prefix)))
|
|
(or (cadr nick-match) prefix ""))))
|
|
|
|
(defun weechat-print-line-data (line-data)
|
|
(let* ((buffer-ptr (assoc-default "buffer" line-data))
|
|
(buffer (weechat--emacs-buffer buffer-ptr)))
|
|
(unless (weechat-buffer-hash buffer-ptr)
|
|
(error "Received new line for '%s' but the buffer doesn't exist in local cache" buffer-ptr))
|
|
(let ((prefix (assoc-default "prefix" line-data))
|
|
(message (assoc-default "message" line-data))
|
|
(date (assoc-default "date" line-data))
|
|
(highlight (assoc-default "highlight" line-data nil 0))
|
|
(line-type (weechat-line-type line-data))
|
|
(invisible (not (= 1 (assoc-default "displayed" line-data nil 0))))
|
|
(nick (weechat--get-nick-from-line-data line-data)))
|
|
;; Handle lines printed to weechat buffers that aren't in weechat-mode
|
|
(when (boundp 'weechat-lines-received)
|
|
(setq weechat-lines-received (+ weechat-lines-received 1)))
|
|
(unless invisible
|
|
(setq highlight (= 1 highlight))
|
|
(when (bufferp (weechat--emacs-buffer buffer-ptr))
|
|
(with-current-buffer buffer
|
|
(when weechat-strip-formatting
|
|
(setq prefix (weechat-strip-formatting prefix))
|
|
(setq message (weechat-strip-formatting message)))
|
|
|
|
;; Nicklist handling. To be replaced with real nicklist
|
|
;; updates when WeeChat starts sending nicklist deltas
|
|
(if (or (and weechat-complete-order-nickname (eq line-type :irc/privmsg))
|
|
(eq line-type :irc/join))
|
|
(weechat--user-list-add nick)
|
|
(cl-case line-type
|
|
(:irc/nick
|
|
(let ((from-nick (weechat--get-nick-from-tag line-data "irc_nick1_"))
|
|
(to-nick (weechat--get-nick-from-tag line-data "irc_nick2_")))
|
|
(when (and from-nick to-nick)
|
|
(weechat--user-list-remove from-nick)
|
|
(weechat--user-list-add to-nick))))
|
|
((:irc/part :irc/quit) (weechat--user-list-remove nick)))))
|
|
|
|
;; Print the line
|
|
(cl-case line-type
|
|
(:irc/action
|
|
(let ((weechat-text-column 0))
|
|
(weechat-print-line buffer-ptr
|
|
:text (concat prefix message)
|
|
:nick nick
|
|
:line-type line-type
|
|
:date date
|
|
:highlight highlight)))
|
|
(t
|
|
(weechat-print-line buffer-ptr
|
|
:prefix prefix
|
|
:text message
|
|
:nick nick
|
|
:date date
|
|
:line-type line-type
|
|
:highlight highlight
|
|
:invisible invisible))))
|
|
|
|
(weechat-update-buffer-modified buffer-ptr line-data)
|
|
|
|
;; TODO: Debug highlight for monitored and un-monitored channels
|
|
;; (Maybe) notify the user
|
|
(with-current-buffer (or (and (buffer-live-p buffer) buffer)
|
|
(get-buffer weechat-relay-log-buffer-name)
|
|
(current-buffer))
|
|
(let* ((buftype (weechat-buffer-type buffer-ptr))
|
|
(highlight (cl-case buftype
|
|
(:private t) ;always highlight queries
|
|
(:server nil) ;never highlight server buffers
|
|
(t highlight)))
|
|
(type (cl-case buftype
|
|
(:private (unless (string=
|
|
(weechat-get-local-var
|
|
"nick"
|
|
buffer-ptr)
|
|
nick)
|
|
:query))
|
|
(:channel :highlight))))
|
|
(when (and (not weechat-inhibit-notifications)
|
|
highlight
|
|
type)
|
|
(weechat-notify type
|
|
:sender nick
|
|
:text message
|
|
:date date
|
|
:buffer-ptr buffer-ptr)))
|
|
|
|
(run-hook-with-args 'weechat-message-post-receive-functions buffer-ptr))))))
|
|
|
|
(defun weechat-add-initial-lines (response)
|
|
(let* ((lines-hdata (car response))
|
|
(hdata-values
|
|
(weechat--hdata-values lines-hdata)))
|
|
(when hdata-values
|
|
(let ((buf-ptr (weechat->
|
|
hdata-values
|
|
(car)
|
|
(weechat--hdata-value-pointer-path)
|
|
(car))))
|
|
;; Need to get buffer-ptr from hdata pointer list
|
|
(with-current-buffer (weechat--emacs-buffer buf-ptr)
|
|
(save-excursion
|
|
(let ((weechat-inhibit-notifications t))
|
|
(dolist (line-hdata (weechat--hdata-values lines-hdata))
|
|
(weechat-print-line-data (weechat--hdata-value-alist line-hdata))))
|
|
(weechat-recenter-bottom-maybe nil 'force)))))))
|
|
|
|
(defvar weechat-initial-lines-buffer-properties
|
|
'("message" "highlight" "prefix" "date" "buffer" "displayed" "tags_array"))
|
|
|
|
(defun weechat-request-initial-lines (buffer-ptr)
|
|
(weechat-relay-log
|
|
(format "Requesting %i lines for buffer %s"
|
|
weechat-initial-lines
|
|
buffer-ptr))
|
|
(weechat-relay-send-command
|
|
(format "hdata buffer:%s/lines/last_line(-%i)/data %s"
|
|
buffer-ptr
|
|
weechat-initial-lines
|
|
(s-join "," weechat-initial-lines-buffer-properties))
|
|
#'weechat-add-initial-lines))
|
|
|
|
(defvar weechat-send-input-last-target nil
|
|
"Internal var used to track last message's target.")
|
|
(defun weechat-send-input (target input)
|
|
(if (not (weechat-connected-p))
|
|
(error "Not connected")
|
|
(when (and weechat-sync-active-buffer
|
|
(not (s-equals? weechat-send-input-last-target
|
|
target)))
|
|
;; HACK: Switch active buffer on the relay server
|
|
(weechat-relay-send-command
|
|
(format "input %s /buffer %s" target (weechat-buffer-name target))))
|
|
(weechat-relay-send-command
|
|
(format "input %s %s" target input))
|
|
(setq weechat-send-input-last-target target)))
|
|
|
|
(defun weechat-get-input ()
|
|
(s-trim-right
|
|
(buffer-substring-no-properties
|
|
weechat-prompt-end-marker
|
|
(point-max))))
|
|
|
|
(defun weechat-replace-input (replacement &optional not-move-eol)
|
|
"Replace input line with REPLACEMENT.
|
|
If NOT-MOVE-EOL is non-nil the point is not changed else it is moved to
|
|
the end of line."
|
|
(save-excursion
|
|
(delete-region weechat-prompt-end-marker (point-max))
|
|
(goto-char weechat-prompt-end-marker)
|
|
(insert (or replacement "")))
|
|
(unless not-move-eol
|
|
(move-end-of-line 1)))
|
|
|
|
(defvar weechat-input-ring)
|
|
|
|
(defun weechat-input-ring-insert (input)
|
|
(unless (ring-member weechat-input-ring input)
|
|
(ring-insert weechat-input-ring input)))
|
|
|
|
(defun weechat-previous-input ()
|
|
(interactive)
|
|
(let ((input (weechat-get-input)))
|
|
;; If input isn't in the ring, assume push it in and show first
|
|
(cond
|
|
((string= input "")
|
|
(weechat-replace-input (ring-ref weechat-input-ring 0)))
|
|
((not (ring-member weechat-input-ring input))
|
|
(weechat-replace-input (ring-ref weechat-input-ring 0))
|
|
(weechat-input-ring-insert input))
|
|
((ring-member weechat-input-ring input)
|
|
(if (string= (ring-ref weechat-input-ring
|
|
(1- (ring-length weechat-input-ring)))
|
|
input)
|
|
(weechat-replace-input "")
|
|
(weechat-replace-input (ring-next weechat-input-ring input)))))))
|
|
|
|
(defun weechat-next-input ()
|
|
(interactive)
|
|
(let ((input (weechat-get-input)))
|
|
;; If input isn't in the ring, assume push it in and show first
|
|
(cond
|
|
((string= input "")
|
|
(weechat-replace-input
|
|
(ring-ref weechat-input-ring
|
|
(1- (ring-length weechat-input-ring)))))
|
|
((not (ring-member weechat-input-ring input))
|
|
(weechat-input-ring-insert input)
|
|
(weechat-replace-input (1- (ring-length weechat-input-ring))))
|
|
((ring-member weechat-input-ring input)
|
|
(if (string= (ring-ref weechat-input-ring 0)
|
|
input)
|
|
(weechat-replace-input "")
|
|
(weechat-replace-input (ring-previous weechat-input-ring input)))))))
|
|
|
|
(defun weechat-pipe-input (text)
|
|
(cl-reduce (lambda (s f)
|
|
(when (stringp s)
|
|
(funcall f s)))
|
|
weechat-message-filter-functions
|
|
:initial-value text))
|
|
|
|
(defun weechat-return ()
|
|
"Return key action.
|
|
If point is in input field send message. If the point is in a chat line
|
|
copy the message. Only the message text is copied unless the prefix argument
|
|
is given (\\[universal-argument])."
|
|
(interactive)
|
|
(cond
|
|
;; Submit
|
|
((>= (point) weechat-prompt-end-marker)
|
|
(let ((input (weechat-get-input)))
|
|
(unless (s-blank? input)
|
|
;; Split multiple lines and send one-by-one
|
|
(cl-dolist (l (split-string input "\n"))
|
|
;; Pipe the input through the message filter system
|
|
(let ((piped-input (weechat-pipe-input l)))
|
|
(when (stringp piped-input)
|
|
(if (and (not (s-equals? piped-input l))
|
|
weechat-message-filter-require-double-ret)
|
|
;; Either filtered text is unchanged or we don't want double-ret anyway
|
|
(progn
|
|
;; Replace text in-place and display a message
|
|
(weechat-replace-input piped-input)
|
|
(message "Input text was filtered..."))
|
|
;; No change, just send it
|
|
(weechat-send-input weechat-buffer-ptr piped-input)
|
|
(weechat-replace-input "")))))
|
|
(weechat-input-ring-insert input))))
|
|
|
|
;; Copy current line to input line
|
|
((< (point) weechat-prompt-start-marker)
|
|
(when (or (s-blank? (weechat-get-input))
|
|
weechat-return-always-replace-input)
|
|
(weechat-replace-input
|
|
(buffer-substring-no-properties
|
|
(if current-prefix-arg
|
|
(point-at-bol)
|
|
(+ (point-at-bol) weechat-text-column))
|
|
(point-at-eol))))
|
|
(goto-char (point-max)))))
|
|
|
|
(defun weechat-self-insert-command (n)
|
|
"Like `self-insert-commands' with automatic cursor movement."
|
|
(interactive "p")
|
|
(when (and weechat-auto-move-cursor-to-prompt
|
|
weechat-read-only
|
|
(< (point) weechat-prompt-start-marker))
|
|
(goto-char (point-max)))
|
|
(self-insert-command n))
|
|
|
|
;;; Make `weechat-self-insert-command' work with some modes
|
|
;;; Borrowed from org.el
|
|
(put 'weechat-self-insert-command 'delete-selection t)
|
|
(put 'weechat-self-insert-command 'flyspell-delayed t)
|
|
(put 'weechat-self-insert-command 'pabbrev-expand-after-command t)
|
|
|
|
(defun weechat-bol (&optional arg)
|
|
"Go to the beginning of line, then skip past the prompt, if any.
|
|
If prefix argument is given (\\[universal-argument]) the prompt is not skipped."
|
|
;; basically copied from `comint-bol'.
|
|
(interactive "P")
|
|
(cond
|
|
(arg (forward-line 0))
|
|
((> (point) weechat-prompt-start-marker) (goto-char weechat-prompt-end-marker))
|
|
(t (beginning-of-line))))
|
|
|
|
(defvar weechat-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(define-key map (kbd "RET") 'weechat-return)
|
|
(define-key map (kbd "M-p") 'weechat-previous-input)
|
|
(define-key map (kbd "M-n") 'weechat-next-input)
|
|
(define-key map (kbd "C-c C-r") 'weechat-reload-buffer)
|
|
(define-key map (kbd "C-c C-g") 'weechat-get-more-lines)
|
|
(define-key map (kbd "TAB") 'completion-at-point)
|
|
(define-key map (kbd "C-a") 'weechat-bol)
|
|
(define-key map (kbd "C-c n l") 'weechat-narrow-to-line)
|
|
(define-key map (kbd "C-c C-b") 'weechat-switch-buffer)
|
|
(define-key map (kbd "C-c C-m") 'weechat-monitor-buffer)
|
|
map)
|
|
"Keymap for weechat mode.")
|
|
|
|
(substitute-key-definition
|
|
'self-insert-command
|
|
'weechat-self-insert-command
|
|
weechat-mode-map
|
|
global-map)
|
|
|
|
(easy-menu-define weechat-mode-menu weechat-mode-map
|
|
"Weechat menu"
|
|
'("WeeChat"
|
|
["Previous Input" weechat-previous-input t]
|
|
["Next Input" weechat-next-input t]
|
|
"-"
|
|
["Narrow To Line" weechat-narrow-to-line
|
|
:active (< (point) weechat-prompt-start-marker)]
|
|
"-"
|
|
["Reload Buffer" weechat-reload-buffer t]
|
|
["Get More Lines" weechat-get-more-lines t]
|
|
["Close Buffer" kill-buffer t]
|
|
["Switch Buffer" weechat-switch-buffer t]
|
|
["Monitor Buffer" weechat-monitor-buffer t]
|
|
["Connect" weechat-connect
|
|
:visible (not (weechat-connected-p))]
|
|
["Disconnect" weechat-disconnect
|
|
:visible (weechat-connected-p)]))
|
|
|
|
(defun weechat-get-local-var (var &optional buffer-ptr)
|
|
"Return value of local VAR in BUFFER-PTR.
|
|
Default is current buffer."
|
|
(or (cdr (assoc-string var
|
|
(gethash "local_variables"
|
|
(weechat-buffer-hash
|
|
(or buffer-ptr weechat-buffer-ptr)))))
|
|
""))
|
|
|
|
(defun weechat-update-header-line-buffer (buffer)
|
|
"Update the header line for BUFFER."
|
|
(with-current-buffer buffer
|
|
(let ((spec (format-spec-make
|
|
?n (weechat-get-local-var "nick")
|
|
?s (weechat-get-local-var "server")
|
|
?c (weechat-get-local-var "channel")
|
|
?N (weechat-get-local-var "name")
|
|
?t weechat-topic)))
|
|
(if weechat-header-line-format
|
|
(setq header-line-format (format-spec weechat-header-line-format spec))
|
|
(setq header-line-format nil)))))
|
|
|
|
(defun weechat-update-header-line (&optional buffer)
|
|
"Update the header line for BUFFER or if BUFFER is nil for all buffers."
|
|
(if (and buffer (bufferp buffer))
|
|
(weechat-update-header-line-buffer buffer)
|
|
(dolist (buffer (weechat-buffer-list))
|
|
(weechat-update-header-line-buffer buffer))))
|
|
|
|
(defun weechat-mode (process buffer-ptr buffer-hash)
|
|
"Major mode used by weechat buffers.
|
|
|
|
\\{weechat-mode-map}"
|
|
|
|
;; Hack to restore prompt location
|
|
(let ((prompt-start (when (boundp 'weechat-prompt-start-marker)
|
|
weechat-prompt-start-marker))
|
|
(prompt-end (when (boundp 'weechat-prompt-end-marker)
|
|
weechat-prompt-end-marker)))
|
|
|
|
(kill-all-local-variables)
|
|
|
|
(puthash :emacs/buffer (current-buffer) buffer-hash)
|
|
(add-hook 'kill-buffer-hook
|
|
(lambda ()
|
|
(when (hash-table-p weechat--buffer-hashes)
|
|
(let ((hash (weechat-buffer-hash weechat-buffer-ptr)))
|
|
(when (hash-table-p hash)
|
|
(remhash :emacs/buffer hash)))))
|
|
nil
|
|
'local-hook)
|
|
|
|
(use-local-map weechat-mode-map)
|
|
(setq mode-name (format "weechat: %s" (weechat-buffer-name buffer-ptr)))
|
|
(setq major-mode 'weechat-mode)
|
|
|
|
(set (make-local-variable 'weechat-buffer-ptr) buffer-ptr)
|
|
(set (make-local-variable 'weechat-server-buffer) (process-buffer process))
|
|
(set (make-local-variable 'weechat-buffer-number) (gethash "number" buffer-hash))
|
|
(set (make-local-variable 'weechat-topic) (gethash "title" buffer-hash))
|
|
(set (make-local-variable 'weechat-lines-received) 0)
|
|
|
|
;; Start with empty user list
|
|
(set (make-local-variable 'weechat-user-list) nil)
|
|
|
|
;; Setup prompt
|
|
(make-local-variable 'weechat-local-prompt)
|
|
(set (make-local-variable 'weechat-prompt-start-marker)
|
|
(or prompt-start(point-max-marker)))
|
|
(set (make-local-variable 'weechat-prompt-end-marker)
|
|
(or prompt-end(point-max-marker)))
|
|
(weechat-update-prompt)
|
|
|
|
;; Initialize input-ring
|
|
(set (make-local-variable 'weechat-input-ring) (make-ring weechat-input-ring-size))
|
|
|
|
;; Don't auto-add newlines on next-line
|
|
(set (make-local-variable 'next-line-add-newlines) nil)
|
|
;; Fix scrolling
|
|
(set (make-local-variable 'scroll-conservatively) 1000)
|
|
(set (make-local-variable 'scroll-margin) 0)
|
|
|
|
;; Initialize buffer
|
|
(weechat-request-initial-lines buffer-ptr)
|
|
|
|
;; Set Header
|
|
(weechat-update-header-line-buffer (current-buffer))
|
|
|
|
;; Hooks
|
|
(run-mode-hooks 'weechat-mode-hook)))
|
|
|
|
(defun weechat--try-ido (&rest args)
|
|
"Complete with ido if available and `completing-read' otherwise."
|
|
(apply (or (and (featurep 'ido)
|
|
(symbol-function 'ido-completing-read))
|
|
#'completing-read)
|
|
args))
|
|
|
|
(defun weechat--try-ivy (&rest args)
|
|
"Complete with ivy if available and `completing-read' otherwise."
|
|
(apply (or (and (featurep 'ivy)
|
|
(symbol-function 'ivy-read))
|
|
#'completing-read)
|
|
args))
|
|
|
|
(defun weechat--read-channel-name (&optional only-monitored)
|
|
"Read channel name from minibuffer in combination with `interactive'."
|
|
(weechat--find-buffer
|
|
(funcall weechat-completing-read-function
|
|
"Channel Name: "
|
|
(weechat-channel-names only-monitored 'sort))))
|
|
|
|
(defun weechat-monitor-buffer (buffer-ptr &optional show-buffer)
|
|
"Start monitoring BUFFER-PTR.
|
|
If SHOW-BUFFER is non-nil `switch-to-buffer' after monitoring it."
|
|
(interactive (list
|
|
(when (weechat-connected-p)
|
|
(weechat--read-channel-name))
|
|
t))
|
|
(if (not (weechat-connected-p))
|
|
(error "Can't monitor buffer, not connected.")
|
|
(save-excursion
|
|
(let* ((buffer-hash (weechat-buffer-hash buffer-ptr))
|
|
(name (weechat-buffer-name buffer-ptr)))
|
|
(unless (hash-table-p buffer-hash)
|
|
(error "Couldn't find buffer %s on relay server" buffer-ptr))
|
|
|
|
;; Notify the user via `weechat-monitor-buffer-function'
|
|
(when weechat-monitor-buffer-function
|
|
(cond
|
|
((eq 'message weechat-monitor-buffer-function)
|
|
(message "Monitoring new Buffer: %s" name))
|
|
((functionp weechat-monitor-buffer-function)
|
|
(with-demoted-errors
|
|
(funcall weechat-monitor-buffer-function buffer-ptr)))))
|
|
|
|
(with-current-buffer (get-buffer-create name)
|
|
(let ((inhibit-read-only t))
|
|
(when (weechat-buffer-p)
|
|
(delete-region (point-min) weechat-prompt-start-marker)))
|
|
(weechat-mode (get-buffer-process weechat-relay-buffer-name)
|
|
buffer-ptr
|
|
buffer-hash)
|
|
(when show-buffer
|
|
(switch-to-buffer (current-buffer))))))))
|
|
|
|
(defun weechat-switch-buffer (buffer-ptr)
|
|
"Like `switch-buffer' but limited to WeeChat buffers.
|
|
|
|
BUFFER-PTR is a string containing a pointer to the buffer to switch to.
|
|
|
|
Will monitor channels if necessary. Will list remotely available buffers if
|
|
called with prefix (\\[universal-argument]), otherwise only monitored buffers."
|
|
(interactive (list (weechat--read-channel-name (not current-prefix-arg))))
|
|
(let ((buffer (weechat--emacs-buffer buffer-ptr)))
|
|
(if (buffer-live-p buffer)
|
|
(switch-to-buffer buffer)
|
|
(weechat-monitor-buffer buffer-ptr 'show))))
|
|
|
|
(defun weechat-reload-buffer (&optional buffer line-count)
|
|
(interactive (list (current-buffer)
|
|
current-prefix-arg))
|
|
(weechat-load-buffer (current-buffer) buffer line-count))
|
|
|
|
(defun weechat-get-more-lines (&optional buffer line-count)
|
|
(interactive (list (current-buffer)
|
|
current-prefix-arg))
|
|
(weechat-load-buffer (current-buffer)
|
|
buffer
|
|
(max (+ weechat-lines-received
|
|
(or line-count weechat-more-lines-amount))
|
|
0)))
|
|
|
|
(defun weechat-load-buffer (current-buffer &optional buffer line-count)
|
|
(if (not (weechat-connected-p))
|
|
(error "Can't reload buffer. Not connected.")
|
|
(with-current-buffer (or buffer (current-buffer))
|
|
(weechat-relay-log
|
|
(format "Re-monitoring buffer %s" (buffer-name buffer)))
|
|
(let ((weechat-initial-lines (or line-count
|
|
weechat-initial-lines)))
|
|
(weechat-monitor-buffer weechat-buffer-ptr)))))
|
|
|
|
(defun weechat-re-monitor-buffers ()
|
|
(interactive)
|
|
(when (or weechat-auto-reconnect-buffers
|
|
(interactive-p))
|
|
(maphash (lambda (_ hash)
|
|
(let ((buffer (and (gethash :emacs/buffer hash)
|
|
(get-buffer (gethash :emacs/buffer hash)))))
|
|
(when (buffer-live-p buffer)
|
|
(weechat-relay-log (format "Re-monitoring buffer %S" buffer) :info)
|
|
(weechat-reload-buffer buffer))))
|
|
weechat--buffer-hashes)))
|
|
|
|
(add-hook 'weechat-connect-hook 'weechat-re-monitor-buffers)
|
|
|
|
(defun weechat-auto-monitor ()
|
|
(let* ((available-channels (weechat-channel-names))
|
|
(chans (cond
|
|
((listp weechat-auto-monitor-buffers)
|
|
weechat-auto-monitor-buffers)
|
|
((stringp weechat-auto-monitor-buffers)
|
|
(cl-remove-if-not (lambda (b)
|
|
(s-matches? weechat-auto-monitor-buffers b))
|
|
available-channels))
|
|
(t (progn
|
|
(weechat-message "Monitoring all available WeeChat buffers. Be patient...")
|
|
available-channels)))))
|
|
;; Either iterate ALL available channels (for `t') or iterate
|
|
;; channels user wants to monitor
|
|
(dolist (channel chans)
|
|
;; Check if one of the available channels partially matches the
|
|
;; channel we want to monitor
|
|
(let* ((channel-name (cl-some
|
|
(lambda (ac)
|
|
;; NOTE: We use `s-suffix?' as we need
|
|
;; to ignore the server-prefix in
|
|
;; `channel'. `s-contains?' causes
|
|
;; errors if two channels share the same
|
|
;; prefix.
|
|
(when (s-suffix? channel ac) ac))
|
|
available-channels))
|
|
(buffer-ptr (weechat--find-buffer channel-name)))
|
|
;; Only auto-connect if it there isn't already a buffer monitoring the channel
|
|
(if buffer-ptr
|
|
(unless (weechat--emacs-buffer buffer-ptr)
|
|
(weechat-relay-log (format "Auto-monitoring buffer %S" channel-name) :info)
|
|
(weechat-monitor-buffer buffer-ptr nil))
|
|
(weechat-warn "Couldn't monitor channel '%s'. Not found." channel))))))
|
|
|
|
(defun weechat-monitor-all-buffers ()
|
|
(interactive)
|
|
(let ((weechat-auto-monitor-buffers t))
|
|
(weechat-auto-monitor)))
|
|
|
|
|
|
(add-hook 'weechat-connect-hook 'weechat-auto-monitor 'append)
|
|
|
|
(defun weechat--handle-buffer-line-added (response)
|
|
(let* ((hdata (car response))
|
|
(line-data (weechat--hdata-value-alist (car (weechat--hdata-values hdata)))))
|
|
(weechat-print-line-data line-data)))
|
|
|
|
(weechat-relay-add-id-callback "_buffer_line_added" #'weechat--handle-buffer-line-added nil 'force)
|
|
|
|
(defun weechat-join (channel)
|
|
"Join CHANNEL."
|
|
(let ((full-name (cl-some (lambda (x) (when (s-contains? channel x) x))
|
|
(weechat-channel-names))))
|
|
(if full-name
|
|
(weechat-monitor-buffer (weechat--find-buffer full-name) 'show)
|
|
(weechat-send-input weechat-buffer-ptr (concat "/join " channel)))))
|
|
|
|
;;; This should probably be in some util file:
|
|
|
|
(defun weechat--send-cmd (cmd &rest options)
|
|
"Send CMD with OPTIONS to WeeChat."
|
|
(weechat-send-input
|
|
weechat-buffer-ptr
|
|
(concat cmd " " (when options
|
|
(cl-reduce (lambda (l r)
|
|
(concat l " " r))
|
|
options)))))
|
|
|
|
(defcustom weechat-nick-operations
|
|
'(("DeOp" . (weechat--send-cmd "/deop" nick))
|
|
("Kick" . (weechat--send-cmd "/kick" nick
|
|
(read-from-minibuffer
|
|
(concat "Kick " nick ", reason: "))))
|
|
("Query" . (weechat--send-cmd "/query" nick))
|
|
("Whois" . (weechat--send-cmd "/whois" nick))
|
|
("Op" . (weechat--send-cmd "/op" nick))
|
|
("Voice" . (weechat--send-cmd "/voice" nick)))
|
|
"An alist of possible nickname actions.
|
|
The format is (\"Action\" . SEXP) wher SEXP is evaluated with `nick' bound."
|
|
:group 'weechat
|
|
:type '(repeat (const (string :tag "Action")
|
|
sexp)))
|
|
|
|
(defun weechat-nick-action (nick)
|
|
"Ask user for action on NICK and `eval' it."
|
|
(let* ((completion-ignore-case t)
|
|
(action (completing-read (concat "What action to take on '" nick "'? ")
|
|
weechat-nick-operations))
|
|
(code `(let ((nick ,nick))
|
|
,(cdr (assoc-string action weechat-nick-operations)))))
|
|
(when code
|
|
(eval code))))
|
|
|
|
(provide 'weechat)
|
|
|
|
;;; weechat.el ends here
|