pax_global_header00006660000000000000000000000064132760101150014506gustar00rootroot0000000000000052 comment=8cbda2738149b070c09288df550781b6c604beb2 weechat.el-0.5.0/000077500000000000000000000000001327601011500135275ustar00rootroot00000000000000weechat.el-0.5.0/.gitignore000066400000000000000000000001021327601011500155100ustar00rootroot00000000000000*.elc *.tar *~ README.html test-cmds.el weechat-pkg.el weechat-*/ weechat.el-0.5.0/Makefile000066400000000000000000000032561327601011500151750ustar00rootroot00000000000000NAME := weechat DESCRIPTION := Chat via WeeChat\'s relay protocol in Emacs EMACS := emacs BATCH := $(EMACS) --batch --eval '(progn (require (quote package)) (package-initialize) (add-to-list (quote load-path) "."))' DATE := $(shell date +%Y%m%d) ifneq ($(wildcard .git),) GITVERSION ?= $(shell git describe --tags --dirty --always) VERSION ?= $(shell git describe --tags --exact-match 2> /dev/null | sed 's/^v//') endif ifeq ($(VERSION),) ifneq ($(wildcard version),) VERSION := $(shell cat version) else VERSION := $(DATE) endif endif PACKAGE := $(NAME)-$(VERSION) TARBALL := $(PACKAGE).tar PKG_EL := $(NAME)-pkg.el PKG_EL_IN := $(PKG_EL).in TESTS := $(wildcard *-test.el) EL := $(wildcard weechat*.el) SOURCES := $(filter-out $(PKG_EL),$(filter-out $(TESTS),$(EL))) PACKAGE_CONTENT := $(SOURCES) Makefile README.org README.html .PHONY: all test doc package clean distclean all: package $(info $(NAME) Version: $(VERSION)) test: $(TESTS) @$(BATCH) -l ert $(foreach file,$^,-l $(file)) -f ert-run-tests-batch-and-exit clean: $(info Cleaning up) @$(RM) $(NAME)-pkg.el README.html README.txt @$(RM) -r $(PACKAGE) @$(RM) $(TARBALL) distclean: clean @$(RM) -r $(TARBALL) README.html: README.org $(info Creating documentation: $@) @$(BATCH) -l org --visit=$< -f org-html-export-to-html doc: README.html $(PKG_EL): $(PKG_EL_IN) sed -e s/@VERSION@/$(VERSION)/ $< > $@ $(TARBALL): $(PKG_EL) doc $(info Creating package tarball $(TARBALL)) @mkdir -p $(PACKAGE) @cp -r $(PACKAGE_CONTENT) $(PKG_EL) $(PACKAGE)/ @echo "$(VERSION)" > $(PACKAGE)/version ifneq ($(GITVERSION),) @echo "$(GITVERSION)" > $(PACKAGE)/git-version endif @tar cf $(TARBALL) $(PACKAGE) package: $(TARBALL) weechat.el-0.5.0/NEWS.org000066400000000000000000000032621327601011500150170ustar00rootroot00000000000000# -*- mode:org; mode:auto-fill; fill-column:80; coding:utf-8; -*- * 0.5 (in Progress) ** Incompatible Changes *** Removal of `weechat-relay-ssl-check-signatures` Previous versions used to advice `gnutls` functions to conditionally enable hostname checking for tls connections. These advices were unstable and could break easily. If you want to keep this feature, you can revert commit bcf714e locally. * 0.4 Changelog is missing * 0.3 ** New Modules *** Commands: =weechat-cmd= Implement IRC commands (/command) in elisp ** New Features *** Nick prefix support =weechat-complete-nick-prefix-alist= *** Read Marker: =weechat-read-marker= *** Ivy Completion Support * 0.2 ** Incompatible Changes *** Notification moved to modules Notification support is now handled by modules. For =notifications.el=-based notifications load the =weechat-notifications= module and for Sauron based notifications the =weechat-sauron= module. ** New Features *** SSL Support *** Color-Handling *** Timestamps and Highlighted Messages *** Input-History *** Auto-Reconnect *** Various other improvements! Check =M-x customize-group RET weechat RET= or experience it yourself! ** New Modules *** Corrector: =weechat-corrector= Correct previous messages with s/foo/bar/ regular expressions. *** Image Previews: =weechat-image= Preview image links in the chat buffer. *** LaTeX Previews: =weechat-latex= Renders LaTeX snippets in the chat buffer. *** Mode-line Activity Indicator: =weechat-tracking= *** Speedbar support: =weechat-speedbar= Use Emacs' =M-x speedbar= to switch between weechat buffers. *** Spell Checking: =weechat-spelling= * 0.1 <2013-02-26 Tue> Initial release weechat.el-0.5.0/README.org000066400000000000000000000165051327601011500152040ustar00rootroot00000000000000# -*- mode:org; mode:auto-fill; fill-column:80; coding:utf-8; -*- * weechat.el - Chat via Weechat in Emacs Please note: This README is work in progress. A more detailed documentation will follow. weechat.el requires Emacs 24 and [[https://github.com/magnars/s.el][s.el]]. For Emacs versions below 24.3 you also need [[http://elpa.gnu.org/packages/cl-lib.html][cl-lib]]. WeeChat version 0.4.0 or newer is recommended! You can install weechat.el via package.el ([[https://stable.melpa.org/][melpa-stable]] (stable releases) or [[http://melpa.milkbox.net/#installing][melpa]] (development snapshot)): : M-x package-install RET weechat RET See [[file:NEWS.org][NEWS.org]] for major changes in releases. ** Manual Installation - Install dependencies via package.el: : M-x package-install RET s RET - If your Emacs is older than 24.3: : M-x package-install RET cl-lib RET - Download this directory to your system and add it to the =load-path=: : (add-to-list 'load-path (expand-file-name "path/to/weechat.el/")) - Load weechat.el : (require 'weechat) (You can add this to your =~/.emacs.d/init.el= or similar) ** Usage First, setup the relay server in weechat. Please refer to the [[http://www.weechat.org/files/doc/stable/weechat_user.en.html#relay_weechat_protocol][manual]]. To load and establish a connection: : (require 'weechat) : M-x weechat-connect RET To show a channel in Emacs, do: : M-x weechat-monitor-buffer RET ** Color settings Most colors in weechat.el come directly from WeeChat and are only translated into Emacs faces. There are a few notable exceptions such as =weechat-highlight-face= or =weechat-nick-self-face=. If you are unsatisfied with the colors that WeeChat send then either change the corresponding color in WeeChat or customize =weechat-color-list=. Do *not* add or remove any values in the list! Simply change the value. Using =rainbow-mode= (from GNU ELPA) or =list-colors-display= can help finding good values. The default configuration tries to match the WeeChat colors as close as possible. Example for colors that go better with the Emacs' default theme are: #+BEGIN_SRC emacs-lisp (setq weechat-color-list '(unspecified "black" "dim gray" "dark red" "red" "dark green" "green" "brown" "orange" "dark blue" "blue" "dark magenta" "magenta" "dark cyan" "royal blue" "dark gray" "gray")) #+END_SRC If you do not want any color then set =weechat-debug-strip-formatting= to =t=. ** SSL See documentation in [[file:SSL.org][SSL.org]]. ** Modules Weechat.el comes with module support. Modules can be loaded by simply calling =load-library= and removed by using =unload-feature=. The variable =weechat-modules= can be customized to set default loaded modules. Available modules are: *** Button This module provides support for buttons in chat windows. E.g., it turns URLs into clickable buttons. The module is default loaded. See =weechat-modules=. It supports several types of buttons such as URLs, Channels, Emacs' symbols, E-Mails, Manpages, Info links, and Nick names. However not all buttons are activated as default. See customization group =weechat-button= to enable and disable specific buttons. You can use =weechat-button-list= to simply add your own button types. *** Complete This module provides support for nickname and command completion. It uses Emacs' =pcomplete= framework and is default loaded. If you want case-insensitive completion, set =completion-ignore-case= to =t=. *** Spelling This module provides spelling support by using Emacs' =flyspell=. You can customize the dictionary on a per channel/server basis by customizing =weechat-spelling-dictionaries=. *** Notifications Weechat.el supports notifications for important messages, such as highlights or queries. The support is either based on =notifications.el= which is shipped with Emacs since version 24 and uses the Freedesktop notification spec. Another solution is based on [[http://www.emacswiki.org/emacs/Sauron][Sauron]]. To activate notifications you have to load the matching module. Either =weechat-notifications= for =notifications.el= or =weechat-sauron= for Sauron support. To change the message types you want to receive notifications for customize =weechat-notification-types=. **** notifications.el Loading the =weechat-notifications= module uses =notifications.el= to display notifications. This uses the Freedesktop notification spec and should work fine on most Linux systems. You can customize =weechat-notifications-sound= to play a sound on notification. Setting =weechat-notifications-icon= allows to change the notification icon. **** Sauron The =weechat-sauron= module uses [[http://www.emacswiki.org/emacs/Sauron][Sauron]] for notifications. *** Tracking The =weechat-tracking= module provides tracking information in the mode line, similar to erc-track. It uses the [[https://github.com/jorgenschaefer/circe/wiki/Tracking][Tracking]] library (available on marmalade or el-get). *** Smiley This module uses Gnus' =smiley-region= support to convert text smileys, such as :-), into a graphical representation. See the documentation of =smiley.el= on how to customize it. *** LaTeX The =weechat-latex= module provides a simple preview function for embedded LaTeX. It is based Org's LaTeX preview functionality and many of Org's LaTex customizations apply to it as well. Use =weechat-latex-preview= to generate previews and =weechat-latex-remove= to remove them. There is also =weechat-latex-auto-mode= to automatically turn LaTeX fragments in every new message into a preview. By using =weechat-latex-preview-region= or =weechat-latex-preview-line= the LaTeX previews can be limited to certain parts of the buffer. *** Speedbar The =weechat-speedbar= module provides Emacs' Speedbar integration. After loading the module and opening the Speedbar there should be a Display mode called "WeeChat" available. *** Image This modules allows (embedded) previews of image urls. After loading the module a button should appear next to urls to image files. By clicking the button images should be opened inline and by clicking the button again they should be removed. By changing =weechat-image-display-func= the images can instead be opened inside the buffer =weechat-image-buffer=. The detection of image URLs can be influenced with =weechat-image-url-regex= and =weechat-image-url-blacklist-regex=. Be careful when loading images of sources you do not trust. Change =weechat-image-size-limit= to prevent the display of large images. * Contact Feel free to contact us via Github, Email, or IRC (#weechat.el on Freenode) We appreciate every comment, suggestion, or nagging for missing features. Tell us your story! * Contributors Please add yourself to this list when you contribute code! - [[https://github.com/the-kenny][Moritz Ulrich]] (Maintainer) - [[https://github.com/ruediger][Rüdiger Sonderfeld]] - [[https://github.com/aristidb][Aristid Breitkreuz]] weechat.el-0.5.0/SSL.org000066400000000000000000000036051327601011500147050ustar00rootroot00000000000000# -*- mode:org; mode:auto-fill; fill-column:80; coding:utf-8; -*- * How to setup SSL ** Important Notes SSL support hasn't been verified by someone who has much experience with encryption. There's absolutely NO guarantee that the connection is secure. ** Generating the SSL Certificate for WeeChat Note: This guide is loosely based on http://frankkoehl.com/2012/02/create-self-signed-wildcard-ssl-certificate/ *** Generate the self-signed SSL Certificate: #+BEGIN_SRC sh mkdir -p ~/.weechat/ssl cd ~/.weechat/ssl # Generate the secret key openssl genrsa 2048 > relay.key # Generate the public certificate # Enter your (sub)domain name for 'Common Name'. All other options are optional openssl req -new -x509 -nodes -days 365 -key relay.key > relay.cert # Generate combined key/certificate file (this is the file weechat needs) cat relay.cert relay.key > relay.pem #+END_SRC ** Configure WeeChat The following will load the SSL certificate in WeeChat and open a ssl-enabled relay on port 9001: #+BEGIN_EXAMPLE /relay sslcertkey /relay add ssl.weechat 9001 #+END_EXAMPLE ** Configure weechat.el - Copy =relay.cert= from your machine running WeeChat to the machine where you run Emacs. - Tell Emacs where =relay.cert= is located: #+BEGIN_SRC elisp (require 'gnutls) ;;; Replace ~/.emacs.d/relay.cert with the location of your 'relay.cert' file (add-to-list 'gnutls-trustfiles (expand-file-name "~/.emacs.d/relay.cert")) #+END_SRC ** Connect via SSL Now you should be able to connect via SSL on the port configured earlier. If you get any errors, re-check the location of your =relay.cert= file. The password is the password you set in weechat using: #+BEGIN_SRC /set relay.network.password "your-secret-password" #+END_SRC weechat.el-0.5.0/default.nix000066400000000000000000000010041327601011500156660ustar00rootroot00000000000000let pkgs = import {}; stdenv = pkgs.stdenv; in { weechat-el = stdenv.mkDerivation rec { name = "weechat-el"; src = ./.; buildInputs = with pkgs; with pkgs.emacs24Packages; [ emacs24 git s cl-lib tracking]; phases = "unpackPhase buildPhase installPhase testPhase"; buildPhase = '' make package ''; installPhase = '' mkdir -p $out cp -r * $out/ ''; testPhase = '' cd $out make test ''; }; } weechat.el-0.5.0/todo.org000066400000000000000000000110601327601011500152030ustar00rootroot00000000000000# -*- mode:org; coding:utf-8; org-pretty-entities:nil; -*- #+STARTUP: nologdone Note: This list is broken on Github (they should really update org-ruby). Please use the 'Raw' link on the right. * Next [4/10] ** TODO Auto-Monitor 'old' buffers Sometimes buffers from an earlier connection remain in Emacs, never getting updated again ** TODO Update a NEWS file Close this right before 0.2 release so it's consistent. ** TODO [#A] Fix missing highlights in Queries etc. ** TODO Use new `ping' command for weechat 0.4.2 We might have to move the ping stuff to weechat.el to implement this in a clean way ** TODO Measure network delay and add configurable hooks for different delays This is useful to trigger a reconnect or warn the user when the delay gets too big. ** DONE Stop auto-reconnect after manual connect ** DONE Different `weechat-tracking-types' for different channels ** DONE Option to truncate long nicks ** DONE Test compatibility with 0.4.2 * 0.2 [4/4] ** DONE Auto-Reconnect ** DONE Don't kill undo-history on buffer change (annoying when you're typing sth.) ** DONE Mode-Line Tracking Indicator [3/3] - [X] Store more change-metadata in buffer-hash - [X] Don't use faces in weechat-tracking.el - [X] Merge branch 'tracking' ** DONE Add a prefix-arg to weechat-reload-buffer ...to load prefix-argument lines * Short-Term [17/24] ** TODO Handle 'sync' events [7/15] - [X] _buffer_opened - [X] _buffer_closing - [X] _buffer_renamed - [X] _buffer_title_changed - [X] _buffer_localvar_added - [X] _buffer_localvar_changed - [ ] _buffer_localvar_removed - [ ] _buffer_type_changed - [X] _buffer_line_added - [ ] _buffer_moved - [ ] _buffer_merged - [ ] _buffer_unmerged - [ ] _nicklist - [ ] _upgrade - [ ] _upgrade_ended ** TODO Mark channel as "read" from Emacs send command: "input 0x10f29d0 /input set_unread_current_buffer" *** This is currently broken on weechat's side. Waiting for FlashCode. ** TODO Handle different types of messages [1/8] Incomplete List (Some of them can be ignored as they print fine with defaults) - [ ] Quit - [X] /me - [ ] Join - [ ] Part - [ ] 'Day Changed' - [ ] Netsplit - [ ] CTCP - [ ] WHOIS ** TODO Check performance with >1000 lines and nick-buttons enabled ** TODO Request more backlog when scrolling / via shortcut ** TODO Fix failed certificate when using gnutls-cli See http://p.tarn-vedra.de/weechat-relay-cert-check-fail.html ** TODO 'Garbage Collect' old ids in `weechat--relay-id-callback-hash' Currently, if the server fails to response, the callback in this hash is never removed. This can lead to a log of garbage data. ** DONE Notify users of new monitored buffers ** DONE package.el package [2/2] *** DONE Auto-Upload to melpa *** DONE marmalade Wait for more stable release. ** DONE Don't delete prompt contents on re-monitor ** DONE :query notification type ** DONE Limit buffer size ** DONE Handle network errors (disconnect) ** DONE Handle opening/closing of buffers (after 'sync') ** DONE Print 'connection lost' message to all buffers ** DONE Input-Ring ** DONE Nick Completion ** DONE List with buffers to "auto-watch" ** DONE Fill region when receiving long messages ** DONE Fix /me display ** DONE Nicklist handling Waiting for delta updates (WeeChat 4.1?) ** DONE Buttons for URLs and other stuff ** DONE Colors Thanks, Rüdiger ** DONE Module System * Nice to have [12/23] ** TODO More Unit Tests (ert) ** TODO Compression ** TODO Faces based on message type ** TODO More Notification Handlers - `message' ** TODO Marker for away state ** TODO Buffer-local URL ring for easy access ** TODO Request all highlighted lines when coming back online ** TODO Get (max 100 (count unread)) lines ** TODO Hooks for everything! ** TODO Re-Implement scrambling of passwords in lambdas Without lexical-let: - Pass symbols around: `make-symbol', `symbol-value' ** DONE Custom commands (defun weechat-cmd-NAME ...) ** TODO Imenu support See http://www.emacswiki.org/emacs/ErcImenu ** DONE Speedbar integration ** DONE Typing auto-focuses the "input field" ** DONE Opening buffers in Emacs should update activation state on weechat side ** DONE Tracking support similar to erc-track. Using circe's tracking.el is probably a good way to implement this https://github.com/jorgenschaefer/circe/blob/master/lisp/tracking.el ** DONE Module for applying s/foo/bar/ message corrections ** DONE SSL Connections ** DONE Sauron Integration ** DONE DBUS-Integration ** DONE Read passwords from ~/.authinfo ** DONE URL Detection ** DONE Buttons for nick names. weechat.el-0.5.0/weechat-button.el000066400000000000000000000332671327601011500170150ustar00rootroot00000000000000;;; weechat-button --- Add buttons to text ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; This code is heavily inspired by erc-button.el! ;;; Code: ;; (require 'weechat) (require 'button) (require 's) ;;; Customize (defgroup weechat-button nil "WeeChat button interface (URLification)." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-button" :group 'weechat) (defcustom weechat-button-url-regexp (concat "\\(www\\.\\|\\(s?https?\\|" "ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\)" "\\(//[-[:alnum:]_.]+:[0-9]*\\)?" "[-[:alnum:]_=!?#$@~`%&*+\\/:;.,()]+[-[:alnum:]_=#$@~`%&*+\\/()]") "Regexp to match URLs. Copied from erc-button.el." :type 'regexp :group 'weechat-button) (defcustom weechat-button-default-log-buffer "*WeeChat URL Log*" "Buffer name for URL log. Valid values include a string describing a buffer name or nil to disable url logging (except when an explicit buffer name is defined in `weechat-button-list')" :group 'weechat-button :type '(choice (const :tag "No Log" nil) (string :tag "Buffer Name"))) (defcustom weechat-button-buttonize-url t "Buttonize url links?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-buttonize-channels t "Buttonize channel links?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-buttonize-symbols t "Buttonize symbol links?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-buttonize-emails nil "Buttonize e-mail link?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-buttonize-man nil "Buttonize manpage links? Format is man(1)." :group 'weechat-button :type 'boolen) (defcustom weechat-button-buttonize-info '("#emacs" "#weechat\\.el") "Buttonize info links? Format is (info \"link\")." :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-rfc-url "http://www.faqs.org/rfcs/rfc%s.html" "URL used to browse rfc references. %s is replaced by the number." :group 'weechat-button :type 'string) (defcustom weechat-button-buttonize-rfc nil "Buttonize rfc links?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) ; temporarily disabled due to performance problems (defcustom weechat-button-buttonize-nicks nil "Buttonize nicknames?" :group 'weechat-button :type '(choice (const :tag "Never" nil) (const :tag "Always" t) (repeat :tag "If regular expression matches buffer name" regexp))) (defcustom weechat-button-list '((weechat-button-url-regexp 0 weechat-button-buttonize-url t "Browse URL" browse-url 0) ("#[-#+_.[:alnum:]]+" 0 weechat-button-buttonize-channels nil "Join Channel" weechat-join 0) ("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]\\{2,4\\}\\b" 0 weechat-button-buttonize-emails nil "email" weechat-button--mailto 0) ("[`]\\([-_.[:alnum:]]+\\)[']" 1 weechat-button-buttonize-symbols nil "Describe Symbol" weechat-button--describe-symbol 1) ("[[:alpha:][:alnum:]]+([1-9])" 0 weechat-button-buttonize-man nil "Manpage" man 0) ("(info \"\\(([[:alnum:]]+) .+?\\)\"" 1 weechat-button-buttonize-info nil "info" info 1) ("\\brfc[#: ]?\\([0-9]+\\)" 0 weechat-button-buttonize-rfc nil "RFC" weechat-button--rfc 1)) "List of potential buttons in WeeChat chat buffers. Each entry has the form (REGEXP BUTTON-MATCH BUTTONIZE? LOG HELP-ECHO ACTION DATA-MATCH...), where REGEXP is a string or variable containing a regular expression to match buttons. BUTTON-MATCH is the number of the regexp grouping which represents the actual button. BUTTONIZE? determines if button should be buttonized. See `weechat-button--buttonize?' for exact usage. LOG decides if `weechat-button-log-functions' gets called. HELP-ECHO is the `help-echo' property of the button. See Info node `(elisp) Button Properties'. ACTION the function to call when the button is selected. DATA-MATCH... numbers of the regexp groupings whose text will be passed to ACTION. This is similar (but not identical) to `erc-button-alist' in ERC." :group 'weechat-button :type '(repeat :tag "Buttons" (list (choice :tag "Matches" regexp (variable :tag "Variable containing regexp")) (integer :tag "Number of the regexp section that matches") (choice :tag "When to buttonize" (const :tag "Always" t) (const :tag "Never" nil) (repeat :tag "If regular expression matches buffer name" regexp) (symbol :tag "Variable name")) (choice :tag "Log match" (const :tag "To default buffer" t) (const :tag "Never" nil) (string :tag "To buffer name")) (string :tag "Help echo text") (function :tag "Call this function when button is pressed") (repeat :tag "Sections of regexp to send to the function" :inline t (integer :tag "Regexp section number"))))) (put 'weechat-button-list 'risky-local-variable t) (defvar weechat-button-log-functions nil "List of function to run when a button should be logged. This hook only runs when `LOG' is set to t for the particular button type. Functions in list must have two arguments: The button data (the match string) and a plist describing the button properties. The functions in this list will be called with `weechat-narrow-to-line' active.") ;;; Internal functions (defun weechat-button--handler (button) "Handle BUTTON actions. The function in property `weechat-function' gets called with `weechat-data'." (let ((function (button-get button 'weechat-function)) (data (button-get button 'weechat-data))) (when function (apply function data)))) (defun weechat-button--log-to-buffer (button-data button-properties) (when (and weechat-button-default-log-buffer) (let ((weechat-buffer-name (buffer-name))) (with-current-buffer (get-buffer-create weechat-button-default-log-buffer) (goto-char (point-max)) (unless (bolp) (insert "\n")) (insert weechat-buffer-name "\t") (apply #'insert-button button-data button-properties) (insert "\n"))))) (add-hook 'weechat-button-log-functions 'weechat-button--log-to-buffer) (defun weechat-button--buttonize? (buttonize?) "Return non-nil if BUTTONIZE? says so. Return non-nil if BUTTONIZE?: - is t, - is a list of strings and one of the strings matches the channel name, - is a variable then apply the rules to the value of the variable." (when (and (symbolp buttonize?) (boundp buttonize?)) (setq buttonize? (symbol-value buttonize?))) (cond ((listp buttonize?) (let ((name (buffer-name)) ret) (dolist (i buttonize? ret) (when (s-matches? i name) (setq ret t))))) (t t))) (defvar weechat-button-log-buffer-last-log nil) (defun weechat-button--add-do (entry &optional text-buttons) "Handle each button ENTRY. If TEXT-BUTTONS is non-nil then use `make-text-button instead of `make-button'." (save-excursion (goto-char (point-min)) (cl-destructuring-bind (regexp-entry button-match buttonize? log help-echo action &rest data-match) entry (let* ((regexp (or (and (stringp regexp-entry) regexp-entry) (and (boundp regexp-entry) (symbol-value regexp-entry)))) (line-date (weechat-line-date)) (run-hooks? (and line-date (or (null weechat-button-log-buffer-last-log) (time-less-p weechat-button-log-buffer-last-log line-date)))) (button-fn (if text-buttons #'make-text-button #'make-button))) (when regexp (while (re-search-forward regexp nil t) (let ((start (match-beginning button-match)) (end (match-end button-match)) (button-data-no-properties (match-string-no-properties button-match)) (data (mapcar #'match-string data-match))) (when (and (weechat-button--buttonize? buttonize?) ;; Don't overlap buttons ;; Handles text-buttons and overlay-buttons (cl-every (lambda (o) (null (overlay-get o 'button))) (overlays-in start end)) (not (text-property-not-all start end 'button nil))) (let ((properties (list 'action #'weechat-button--handler 'help-echo help-echo 'follow-link t 'weechat-function action 'weechat-data data))) (when (and log run-hooks?) ;; Hack: Rebind `weechat-button-default-log-buffer' ;; to the value supplied by the button type in ;; `weechat-button-list' (let ((weechat-button-default-log-buffer (if (or (stringp log) (bufferp log)) log weechat-button-default-log-buffer))) (run-hook-with-args 'weechat-button-log-functions button-data-no-properties properties)) (setq weechat-button-log-buffer-last-log line-date)) (apply button-fn start end properties)))))))))) (defun weechat-button--add () "Add text buttons to text in buffer." (dolist (i weechat-button-list) (weechat-button--add-do i)) (when weechat-button-buttonize-nicks (weechat-button--add-nickname-buttons))) (defvar weechat-user-list) ;; See weechat.el (defun weechat-button--add-nickname-buttons () "Add nick name buttons." (dolist (nick weechat-user-list) (unless (s-blank? nick) (weechat-button--add-do (list (concat "\\b" (regexp-quote nick) "\\b") 0 t 0 "Nick Action" #'weechat-nick-action 0) 'text-button)))) ;;; Callback functions ;; This function is copied from `erc-button-describe-symbol' (defun weechat-button--describe-symbol (symbol-name) "Describe SYMBOL-NAME. Use `describe-function' for functions, `describe-variable' for variables, and `apropos' for other symbols." (let ((symbol (intern-soft symbol-name))) (cond ((and symbol (fboundp symbol)) (describe-function symbol)) ((and symbol (boundp symbol)) (describe-variable symbol)) (t (apropos symbol-name))))) (defun weechat-button--mailto (email) "Call `browse-url' on email with \"mailto:\" prepend." (browse-url (concat "mailto:" email))) (defun weechat-button--rfc (rfc) "Call `browse-url' on RFC using `weechat-button-rfc-url'." (browse-url (format weechat-button-rfc-url rfc))) ;;; Module load/unload ;;; This is done automatically by `load-library' or `require'. ;;; Unloading is taken care of, because hooks added via `add-hook' ;;; will be removed automatically by `unload-feature'. ;;; If you need special cleanup code, use define a function named ;;; `FEATURE-unload-function'. This function will be called by emacs ;;; right before unloading the feature. Check the docstring of ;;; `unload-feature' for details. (add-hook 'weechat-insert-modify-hook #'weechat-button--add) (provide 'weechat-button) ;;; weechat-button.el ends here weechat.el-0.5.0/weechat-cmd.el000066400000000000000000000051031327601011500162310ustar00rootroot00000000000000;;; weechat-cmd.el --- Define local weechat commands in elisp ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; This modules allows the user to write Emacs Lisp functions which ;; are callable in weechat buffers via normal irc /command syntax. ;; ;; Function names must start with `weechat-cmd-function-prefix' and ;; take one argument, the input of the user. ;; ;; They might return either a string or nil. In case of string the ;; value will be sent to the server. If the return value is nil, the ;; command will be discarded. This is useful if you want to execute ;; local code but not send anything to the server. ;;; Code: (require 'weechat) (require 's) (require 'cl-lib) (defconst weechat-cmd-function-prefix "weechat-command-") (defcustom weechat-cmd-prefix "/" "Prefix used for cmd commands." :type 'string :group 'weechat-cmd) (defun weechat-cmd-find-cmds () (let (ret) (mapatoms (lambda (x) (when (s-prefix? weechat-cmd-function-prefix (symbol-name x)) (setq ret (cons x ret))))) ret)) (defun weechat-cmd-apply (input) (if (s-prefix? weechat-cmd-prefix input) (let* ((command (car (s-split-words (s-chop-prefix weechat-cmd-prefix input)))) (sym (intern-soft (concat weechat-cmd-function-prefix command))) (fn (when (fboundp sym) (symbol-function sym)))) (if (functionp fn) (funcall fn input) input)) input)) (add-hook 'weechat-message-filter-functions #'weechat-cmd-apply) (provide 'weechat-cmd) ;;; weechat-cmd.el ends here weechat.el-0.5.0/weechat-color.el000066400000000000000000000412351327601011500166120ustar00rootroot00000000000000;;; weechat-color --- Color handling for WeeChat ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Moritz Ulrich, Rüdiger Sonderfeld ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; WeeChat comes with its own (ridiculously complicated) set of color codes. ;; See URL `http://www.weechat.org/files/doc/devel/weechat_dev.en.html#color_codes_in_strings' ;;; Code: (require 'weechat-core) (require 'rx) (require 's) (defgroup weechat-faces nil "WeeChat Faces and Color settings" :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-" :group 'weechat) (defface weechat-nick-self-face '((t :weight bold :foreground "brown")) "Face for your own nick." :group 'weechat-faces) (defface weechat-time-face '((t :inherit default)) "Weechat face used for timestamps." :group 'weechat-faces) (defface weechat-prompt-face '((t :inherit minibuffer-prompt)) "Weechat face used for the prompt." :group 'weechat-faces) (defface weechat-highlight-face '((((class grayscale) (background light)) :foreground "LightGray" :weight bold) (((class grayscale) (background dark)) :foreground "DimGray" :weight bold) (((class color) (min-colors 88) (background light)) :foreground "Purple") (((class color) (min-colors 88) (background dark)) :foreground "Cyan1") (((class color) (min-colors 16) (background light)) :foreground "Purple") (((class color) (min-colors 16) (background dark)) :foreground "Cyan") (((class color) (min-colors 8)) :foreground "cyan" :weight bold) (t :weight bold)) ;; Stolen from rcirc.el! "Weechat face for highlighted lines." :group 'weechat-faces) (defface weechat-error-face '((t :inherit error)) "Weechat face used to display errors." :group 'weechat-faces) (defcustom weechat-strip-formatting nil "Remove every kind of formatting or color from messages. This will look very bland!" :type 'boolean :group 'weechat-faces) (defvar weechat-formatting-regex (let* ((attr `(in "*!/_|")) ;NOTE:  is not documented (std `(= 2 digit)) (astd `(seq ,attr (= 2 digit))) (ext `(seq "@" (= 5 digit))) (aext `(seq "@" ,attr (= 5 digit)))) (rx-form `(or (seq "" (or ,std ,ext (seq "F" (or ,std ,astd ,ext ,aext)) (seq "B" (or ,std ,ext)) (seq "*" (or ,std ,astd ,ext ,aext (seq (or ,std ,astd ,ext ,aext) "," (or ,std ,astd ,ext ,aext)))) (seq "b" (in "FDB_-#il")) "")) (seq "" ,attr) (seq "" ,attr) "")))) (defun weechat-strip-formatting (string) "Strip weechat color codes from STRING." (if (stringp string) (replace-regexp-in-string weechat-formatting-regex "" string) string)) (defcustom weechat-color-list '(unspecified "black" "dark gray" "dark red" "red" "dark green" "light green" "brown" "yellow" "dark blue" "light blue" "dark magenta" "magenta" "dark cyan" "light cyan" "gray" "white") "Mapping of Weechat colors. Do NOT remove or add new elements to the list. Only change the values. See URL `http://www.weechat.org/files/doc/devel/weechat_dev.en.html#color_codes_in_strings'." :type '(repeat (choice (const :tag "Unspecified" unspecified) (const :tag "Color" color))) :group 'weechat) (defvar weechat-color-attributes-alist '((?* . (:weight bold)) (?\1 . (:weight bold)) ; bold (?! . (:inverse-video t)) (?\2 . (:inverse-video t)) ; reverse?? (?/ . (:slant italic)) (?\3 . (:slant italic)) ; italic (?_ . (:underline t)) (?\4 . (:underline t)) ; underline (?| . keep)) ; keep "Map color attribute specifiers to Emacs face property.") (defvar weechat-terminal-colors (concat "000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e5" "4d4d4dff000000ff00ffff000000ffff00ff00ffffffffff" "00000000002a0000550000800000aa0000d4002a00002a2a" "002a55002a80002aaa002ad400550000552a005555005580" "0055aa0055d400800000802a0080550080800080aa0080d4" "00aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a" "00d45500d48000d4aa00d4d42a00002a002a2a00552a0080" "2a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4" "2a55002a552a2a55552a55802a55aa2a55d42a80002a802a" "2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80" "2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d4" "55000055002a5500555500805500aa5500d4552a00552a2a" "552a55552a80552aaa552ad455550055552a555555555580" "5555aa5555d455800055802a5580555580805580aa5580d4" "55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a" "55d45555d48055d4aa55d4d480000080002a800055800080" "8000aa8000d4802a00802a2a802a55802a80802aaa802ad4" "80550080552a8055558055808055aa8055d480800080802a" "8080558080808080aa8080d480aa0080aa2a80aa5580aa80" "80aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4" "aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2a" "aa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580" "aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4" "aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a" "aad455aad480aad4aaaad4d4d40000d4002ad40055d40080" "d400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4" "d45500d4552ad45555d45580d455aad455d4d48000d4802a" "d48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80" "d4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d4" "0808081212121c1c1c2626263030303a3a3a4444444e4e4e" "5858586262626c6c6c7676768080808a8a8a9494949e9e9e" "a8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee")) (defun weechat--match-color-code (what str i) "Match std, ext, attr WHAT on STR at position I. This is internal and used by `weechat-handle-color-codes'." (when (symbolp what) (cl-case what ((std) (let ((std (substring str i (+ i 2)))) (when (s-matches? "^[0-9]+$" std) (list 'std (+ i 2) (string-to-number std))))) ((ext) (when (= (aref str i) ?@) (let ((std (substring str (1+ i) (+ i 6)))) (when (s-matches? "^[0-9]+$" std) (list 'ext (+ i 6) (string-to-number std)))))) ((attr) (let* ((a (aref str i)) (x (cdr (assq a weechat-color-attributes-alist)))) (when x (list 'attr (1+ i) x)))) (t (error "Unknown parameter %s" what))))) (defun weechat--color-keep-attributes (old-face) "Remove color settings from OLD-FACE but keep the attributes." (cl-delete-if (lambda (x) (memq (car x) '(:foreground :background))) old-face)) (defun weechat--rgb-color (index) (let* ((colors (substring weechat-terminal-colors (* index 6))) (r (* 0.85 (string-to-number (substring colors 0 2) 16))) (g (* 0.85 (string-to-number (substring colors 2 4) 16))) (b (* 0.85 (string-to-number (substring colors 4 6) 16)))) (format "#%02x%02x%02x" r g b))) (defun weechat--color-handle-F (str i old-face) "Handle ?F (A)STD|(A)EXT color code in STR at I with OLD-FACE. This is an internal function of `weechat-handle-color-codes'." (let (match-data face (j (1+ i))) (while (setq match-data (weechat--match-color-code 'attr str j)) ;; (A) (if (eq (cl-third match-data) 'keep) (setq face (weechat--color-keep-attributes old-face)) (setq face (append (list (cl-third match-data)) face))) (setq j (cl-second match-data))) (setq match-data (weechat--match-color-code 'std str j)) (if match-data (setq face (append (list (list :foreground (nth (cl-third match-data) weechat-color-list))) face)) ;; TODO set attribute instead of simply append (setq match-data (weechat--match-color-code 'ext str j)) (if match-data (setq face (list (list :foreground (weechat--rgb-color (cl-third match-data))))) (weechat-relay-log (format "Broken color code (in ?F '%s' %s)" str i) :warn))) (if match-data (cl-values (cl-second match-data) face) (cl-values j face)))) (defvar weechat-color-options-list '(("weechat.color.separator" . "blue") ;; KEEP THE ORDER! ("weechat.color.chat" . default) ("weechat.color.chat_time" . weechat-time-face) ("weechat.color.chat_time_delimiters" . weechat-time-face) ("weechat.color.chat_prefix_error" . "yellow") ("weechat.color.chat_prefix_network" . "magenta") ("weechat.color.chat_prefix_action" . "white") ("weechat.color.chat_prefix_join" . "light green") ("weechat.color.chat_prefix_quit" . "orange red") ;; light red ("weechat.color.chat_prefix_more" . "medium violet red") ;; light magenta ("weechat.color.chat_prefix_suffix" . "green") ("weechat.color.chat_buffer" . "white") ("weechat.color.chat_server" . "brown") ("weechat.color.chat_channel" . "white") ("weechat.color.chat_nick" . "light cyan") ("weechat.color.chat_nick_self" . weechat-nick-self-face) ("weechat.color.chat_nick_other" . "cyan") (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) (nil . default) ("weechat.color.chat_host" . "cyan") ("weechat.color.chat_delimiters" . "green") ("weechat.color.chat_highlight" . weechat-highlight-face) ("weechat.color.chat_read_marker" . "magenta") ("weechat.color.chat_text_found" . "yellow") ("weechat.color.chat_value" . "cyan") ("weechat.color.chat_prefix_buffer") ("weechat.color.chat_tags" . "red") ("weechat.color.chat_inactive_window" . "dark grey") ("weechat.color.chat_inactive_buffer" . "dark grey") ("weechat.color.chat_prefix_buffer_inactive_buffer" . "dark grey") ("weechat.color.chat_nick_offline" . "dark grey") ("weechat.color.chat_nick_offline_highlight" . default)) "List of color options for \x19XX.") ;; TODO every option here should probably be a face! (defun weechat--color-std-to-theme (num) "Turn color code in NUM using option into face." (if (or (not (integerp num)) (> num (length weechat-color-options-list))) 'default (let ((face (cdr (nth num weechat-color-options-list)))) (if (stringp face) ;; color value (list (list :foreground face )) face)))) (defun weechat-handle-color-codes (str) "Convert the Weechat color codes in STR to properties. EXT colors are currently not supported. Any color codes left are stripped. Be aware that Weechat does not use mIRC color codes. See URL `http://www.weechat.org/files/doc/devel/weechat_dev.en.html#color_codes_in_strings'." (let ((i 0) face (ret "") (len (length str))) (while (< i len) (cl-case (aref str i) ((?\x19) ;; STD|EXT|?F((A)STD|(A)EXT)|?B(STD|EXT)|?\x1C|?*...|?b... (let ((old-face face) (next (aref str (1+ i)))) (setq face nil) (setq i (1+ i)) (cond ((and (<= ?0 next) (<= next ?9)) ;; STD (let ((match-data (weechat--match-color-code 'std str i))) (when match-data (setq face (weechat--color-std-to-theme (cl-third match-data))) (setq i (cl-second match-data))))) ((= next ?@) ;; EXT (let ((match-data (weechat--match-color-code 'ext str i))) (when match-data ;; TODO (setq i (cl-second match-data))))) ((= next ?F) ;; ?F(A)STD|?F(A)EXT (cl-multiple-value-setq (i face) (weechat--color-handle-F str i old-face))) ((= next ?B) ;; ?BSTD|?BEXT (let ((match-data (weechat--match-color-code 'std str i))) (if match-data (setq face (list (list :background (nth (cl-third match-data) weechat-color-list)))) (setq match-data (weechat--match-color-code 'ext str i)) (if match-data t ;; TODO ext (weechat-relay-log (format "Broken color code (in ?B '%s' %s)" str i) :warn))) (setq i (if match-data (cl-second match-data) (1+ i))))) ((= next ?*) ;; (A)STD | (A)EXT | (A)STD ?, (A)STD | ... (cl-multiple-value-setq (i face) (weechat--color-handle-F str i old-face)) (if (= (aref str i) ?,) (let* ((i (1+ i)) (match-data (weechat--match-color-code 'std str i))) (if match-data (setq face (append (list (list :background (nth (cl-third match-data) weechat-color-list))) face)) (setq match-data (weechat--match-color-code 'ext str i)) (if match-data t ;; TODO ext (weechat-relay-log (format "Broken color code (in ?* '%s' %s)" str i) :warn))) (setq i (if match-data (cl-second match-data) (1+ i)))))) ((= next ?b) 'b) ;; ignore for now ((= next ?\x1C) ;; Clear color, leave attributes (setq face (weechat--color-keep-attributes old-face)))))) ((?\x1A) ;; Set ATTR (let ((match-data (weechat--match-color-code 'attr str (1+ i)))) (if (not match-data) (progn (weechat-relay-log (format "Broken color code (in ?\\x1A '%s' %s)" str i) :warn) (setq i (1+ i))) (if (eq (cl-third match-data) 'keep) (setq face (weechat--color-keep-attributes face)) (setq face (list (cl-third match-data)))) (setq i (cl-second match-data))))) ((?\x1B) ;; Delete ATTR (let ((match-data (weechat--match-color-code 'attr str (1+ i))) (old-face (copy-sequence face))) (if (not match-data) (progn (weechat-relay-log (format "Broken color code (in ?\\x1B '%s' %s)" str i) :warn) (setq i (1+ i))) (if (eq (cl-third match-data) 'keep) (setq face nil) ;; TODO Does keep here means delete all or keep all? (setq face (delq (cl-third match-data) old-face))) (setq i (cl-second match-data))))) ((?\x1C) (setq i (1+ i) face nil))) ;; reset face (let ((r (string-match-p "\\(\x19\\|\x1A\\|\x1B\\|\x1C\\)" str i))) (if r (setq ret (concat ret (propertize (substring str i r) 'face (or face 'default))) i r) (setq ret (concat ret (propertize (substring str i) 'face (or face 'default))) i len)))) ;; STOP ret)) (provide 'weechat-color) ;;; weechat-color.el ends here weechat.el-0.5.0/weechat-complete.el000066400000000000000000000160061327601011500173020ustar00rootroot00000000000000;;; weechat-complete --- completions for weechat.el. ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Parts copied from `erc-pcomplete.el' are ;; Copyright (C) 2002-2004, 2006-2013 Free Software Foundation, Inc. ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; Martin Yrjölä ;; 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 . ;;; Commentary: ;; Completions for weechat.el. Parts of the code are copied from ;; `erc-pcomplete.el'. ;;; Code: (require 'weechat) (require 'pcomplete) (require 's) (defvar weechat-user-list) (defvar weechat-prompt-end-marker) (defcustom weechat-complete-nick-prefix-and-postfix-alist nil "Alist of nick prefixes and postfixes to use for certain buffers Each element looks like (BUFFER-NAME-REGEXP . (NICK-PREFIX . NICK-POSTFIX). If BUFFER-NAME-REGEXP matches the current buffer, then NICK-PREFIX is pushed to the beginning of each nick in `weechat-user-list' and NICK-POSTFIX is appended to them. One common use case is that a messaging service provides an IRC server (e.g. Flowdock and Gitter), but the mentions have to be prefixed with '@' to be highlighted in the other clients." :type '(alist :key-type regexp :value-type (cons string string)) :group 'weechat) (defcustom weechat-complete-nick-postfix ":" "Postfix to nick completions at the beginning of the prompt. Can be overriden by `weechat-complete-nick-prefix-and-postfix-alist'" :type 'string :group 'weechat) (defcustom weechat-complete-nick-ignore-self t "Wether to ignore yourself when completing at the begginning of the input line Make sure to rebuild each buffer after changing this variable." :type 'boolean :group 'weechat) (defun weechat-pcompletions-at-point () "WeeChat completion data from pcomplete. for use on `completion-at-point-function'. Copied from `erc-pcompletions-at-point'." (when (and (eq major-mode 'weechat-mode) (>= (point) weechat-prompt-end-marker)) (or (let ((pcomplete-default-completion-function #'ignore)) (pcomplete-completions-at-point)) (let ((c (pcomplete-completions-at-point))) (if c (nconc c '(:exclusive no))))))) (defun pcomplete-weechat-setup () "Setup pcomplete for `weechat-mode'." (set (make-local-variable 'pcomplete-ignore-case) t) (set (make-local-variable 'pcomplete-use-paring) nil) (set (make-local-variable 'pcomplete-parse-arguments-function) #'pcomplete-weechat-parse-arguments) (set (make-local-variable 'pcomplete-command-completion-function) #'pcomplete/weechat/complete-command) (set (make-local-variable 'pcomplete-command-name-function) #'pcomplete-weechat-command-name) (set (make-local-variable 'pcomplete-default-completion-function) (lambda () (pcomplete-here (pcomplete-weechat-nicks))))) (defun pcomplete-weechat-parse-arguments () "Return a list of parsed whitespace-separated arguments. These are the words from the beginning of the line after the prompt up to where point is right now. Copied from `pcomplete-erc-parse-arguments'." (let* ((start weechat-prompt-end-marker) (end (point)) args beginnings) (save-excursion (when (< (skip-chars-backward " \t\n" start) 0) (setq args '("") beginnings (list end))) (setq end (point)) (while (< (skip-chars-backward "^ \t\n" start) 0) (setq beginnings (cons (point) beginnings) args (cons (buffer-substring-no-properties (point) end) args)) (skip-chars-backward " \t\n" start) (setq end (point)))) (cons args beginnings))) (defun pcomplete-weechat-command-name () "Return the command name of the first argument. Copied from `pcomplete-erc-command-name'." (if (eq (aref (pcomplete-arg 'first) 0) ?/) (upcase (substring (pcomplete-arg 'first) 1)) "SAY")) (defun pcomplete/weechat/complete-command () "Complete the initial command argument." (pcomplete-here (append (pcomplete-weechat-commands) (pcomplete-weechat-nicks weechat-complete-nick-postfix weechat-complete-nick-ignore-self)))) (defun pcomplete/weechat-mode/WHOIS () (pcomplete-here (pcomplete-weechat-all-nicks))) (defun pcomplete/weechat-mode/QUERY () (pcomplete-here (pcomplete-weechat-all-nicks))) (defun pcomplete/weechat-mode/SAY () (while (pcomplete-here (pcomplete-weechat-nicks)))) (defun pcomplete-weechat-commands () "Return a list of user commands." '("/NICK" "/JOIN" "/PART" "/WHOIS" "/QUERY")) ;; TODO (defun pcomplete-weechat-nicks (&optional postfix ignore-self) "Return a list of nicks in the current channel. POSTFIX is an optional string to append to the nickname. If IGNORE-SELF is non-nil the users nick is ignored." (let* ((users weechat-user-list) (prefix-and-postfix (weechat-complete-nick-prefix-and-postfix-for-current-buffer)) (nick-prefix (car prefix-and-postfix)) (nick-postfix (if prefix-and-postfix (cdr prefix-and-postfix) postfix))) (when ignore-self (setq users (delete (weechat-get-local-var "nick") users))) (mapcar (lambda (nick) (concat nick-prefix nick nick-postfix)) users))) (defun weechat-complete-nick-prefix-and-postfix-for-current-buffer () "Returns the nick-prefix-and-postfix for the current buffer. The format is (nick-prefix . nick-postfix)." (let ((found-nick-prefix-and-postfix nil)) (mapc (lambda (element) (let ((buffer-name-regexp (car element)) (nick-prefix-and-postfix (cdr element))) (when (string-match buffer-name-regexp (buffer-name)) (setq found-nick-prefix-and-postfix nick-prefix-and-postfix)))) weechat-complete-nick-prefix-and-postfix-alist) found-nick-prefix-and-postfix)) (defun pcomplete-weechat-all-nicks () "Return nick list of all weechat buffers." (let (result) (weechat-do-buffers (setq result (cl-union weechat-user-list result :test #'s-equals?))) result)) (weechat-do-buffers (pcomplete-weechat-setup)) (add-hook 'weechat-mode-hook #'pcomplete-weechat-setup) (add-hook 'completion-at-point-functions #'weechat-pcompletions-at-point) (provide 'weechat-complete) ;;; weechat-complete.el ends here weechat.el-0.5.0/weechat-core.el000066400000000000000000000066121327601011500164240ustar00rootroot00000000000000;;; weechat-core --- Basic stuff for weechat.el ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; 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 'cl-lib) (require 's) (defgroup weechat nil "WeeChat based IRC client for Emacs." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-" :group 'applications) (defgroup weechat-relay nil "WeeChat Relay Settings." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-relay" :group 'weechat) (defcustom weechat-relay-log-buffer-name "*weechat-relay-log*" "Buffer name to use as debug log. Set to nil to disable logging." :type 'string :group 'weechat-relay) (defcustom weechat-relay-log-level :info "Minimum log level. Might be one of :debug, :info, :warn, :error or nil." :type '(choice (const :tag "Off" nil) (const :tag "Error" :error) (const :tag "Warnings" :warn) (const :tag "Info" :info) (const :tag "Debug" :debug)) :group 'weechat-relay) (defun weechat-relay-log (text &optional level) "Log `TEXT' to `weechat-relay-log-buffer-name' if enabled. `LEVEL' might be one of :debug :info :warn :error. Defaults to :info" (let ((log-level-alist '((:debug . 0) (:info . 1) (:warn . 2) (:error . 3)))) (when (and (>= (assoc-default (or level :info) log-level-alist) (assoc-default weechat-relay-log-level log-level-alist)) weechat-relay-log-level weechat-relay-log-buffer-name) (with-current-buffer (get-buffer-create weechat-relay-log-buffer-name) (let ((old-point (point))) (save-excursion (save-restriction (widen) (goto-char (point-max)) (insert (s-trim text)) (newline))) (goto-char old-point)))))) (defun weechat-warn (message &rest args) "Display MESSAGE with `warn' and log it to `weechat-relay-log-buffer-name'." (let ((str (apply 'format message args))) (weechat-relay-log str :warn) (display-warning 'weechat str))) (defun weechat-message (format-string &rest args) "Log MESSAGE with log-level :info and call `message'." (let ((text (apply 'format format-string args))) (weechat-relay-log text :info) (message text))) (provide 'weechat-core) ;;; weechat-core.el ends here weechat.el-0.5.0/weechat-corrector.el000066400000000000000000000103161327601011500174720ustar00rootroot00000000000000;;; weechat-corrector.el --- Fix your messages using s/foo/bar/ syntax ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; This module implements support to fix your own messages via the ;; s/foo/bar/ syntax. ;;; Code: (require 'weechat) (require 's) (defgroup weechat-corrector nil "This module implements support to fix your own messages via the s/foo/bar/ syntax." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-corrector" :group 'weechat) (defcustom weechat-corrector-search-limit 5 "How many previous lines to check for corrections." :type 'integer :group 'weechat-corrector) (defcustom weechat-corrector-replace-limit 1 "Limit to N replacements." :type 'integer :group 'weechat-corrector) (defcustom weechat-corrector-correct-other nil "Whether to apply corrections by other people. Warning: Setting this to non-nil MIGHT be a security problem as untrusted regular expression will be interpreted by `re-search-forward'." :type 'boolean :group 'weechat-corrector) (defcustom weechat-corrector-support-plain-parentheses nil "If non-nil, s/a(.)c/\1/ will replace 'abc' with 'b'. If nil, parentheses must be quotedL s/a\(.\)c/\1/." :type 'boolean :group 'weechat-corrector) (defface weechat-corrector-corrected-face '((t :inherit default)) "Face used to highlight corrected text." :group 'weechat-corrector) (defun weechat-corrector-quote-parentheses (re) (if weechat-corrector-support-plain-parentheses (weechat->> re (s-replace "(" "\\(") (s-replace ")" "\\)")) re)) (defvar weechat-corrector-regex "s/\\(.+\\)/\\(.*\\)/") (defun weechat-corrector-apply () (let ((nick (weechat-line-nick)) (line (weechat-line-text))) (when (and (or weechat-corrector-correct-other (string= (weechat-line-nick) (weechat-get-local-var "nick"))) line (stringp line)) (let* ((text-start (weechat-line-text-start)) (match (s-match weechat-corrector-regex line))) (when (>= (length match) 3) (let ((re (weechat-corrector-quote-parentheses (cadr match))) (rp (caddr match))) (save-excursion (save-restriction (widen) (goto-char (point-at-bol)) (let ((count 0)) (dotimes (i weechat-corrector-search-limit) (when (< count weechat-corrector-replace-limit) (save-restriction (let ((line-move-visual nil)) (forward-line -1)) (weechat-narrow-to-line) (goto-char (weechat-line-text-start)) (when (and (string= nick (weechat-line-nick)) (re-search-forward re nil t)) (replace-match rp) ;; Add `weechat-corrector-corrected-face' (add-text-properties (match-beginning 0) (match-end 0) '(face weechat-corrector-corrected-face)) (setq count (1+ count))))))))))))))) (add-hook 'weechat-insert-modify-hook 'weechat-corrector-apply) (provide 'weechat-corrector) ;;; weechat-corrector.el ends here weechat.el-0.5.0/weechat-image.el000066400000000000000000000310221327601011500165470ustar00rootroot00000000000000;;; weechat-image --- Image preview ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; TODO: resize, make buffer more beautiful, test test test ;;; Code: ;; (require 'weechat) ;;; Customize: ;; (defgroup weechat-image nil "Image previews for WeeChat." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-image" :group 'weechat) (defcustom weechat-image-url-regex "\\.\\(png\\|jpe?g\\|gif\\|svg\\)" "Regexp to match image URLs. This gets called on a URL matched with `thing-at-point' and `url'." :type 'regexp :group 'weechat-image) (defcustom weechat-image-url-blacklist-regex "/\\(Datei\\|File\\):" "Blacklist for image URLs. E.g., for Wikipedia links starting with File:. They do not link directly to the image." :type 'regexp :group 'weechat-image) (defcustom weechat-image-display-func #'weechat-image-insert-inline "Function to call to insert image. The Function should accept the following parameter (URL IMAGE BUFFER MARKER)." :type '(choice (const :tag "Inline" weechat-image-insert-inline) (const :tag "Other Buffer" weechat-image-insert-other-buffer) (function :tag "Call function")) :group 'weechat-image) (defcustom weechat-image-buffer "*WeeChat Image Buffer*" "Buffer used if `weechat-image-display-func' is set to ``Other Buffer''." :type 'string :group 'weechat-image) (defcustom weechat-image-use-imagemagick (fboundp 'imagemagick-types) ;; TODO is there a better way to identify if emacs has imagemagick support? "Use ImageMagick to load images." :type 'boolean :group 'weechat-image) (defcustom weechat-image-size-limit (* 1024 1024) ;; 1M "Size limit for images." :type '(choice (const :tag "No limit" nil) (integer :tag "Size limit in bytes")) :group 'weechat-image) (defcustom weechat-image-max-width (/ (frame-pixel-width nil) 2) "Max image width. If `weechate-image-size' is non-nil the image is resized. Be aware that `weechat-image-size-limit' is checked before." :type '(choice (const :tag "No limit" nil) (integer :tag "Max width in pixel")) :group 'weechat-image) (defcustom weechat-image-max-height nil "Max image height. If `weechate-image-size' is non-nil the image is resized. Be aware that `weechat-image-size-limit' is checked before." :type '(choice (const :tag "No limit" nil) (integer :tag "Max height in pixel")) :group 'weechat-image) (defcustom weechat-image-resize weechat-image-use-imagemagick "Resize image if it's larger than `weechat-image-max-width' and `weechat-image-max-height'. This only works if imagemagick is used. See `weechat-image-use-imagemagick'." :type 'boolean :group 'weechat-image) (defcustom weechat-image-time-format "%Y-%m-%dT%T%z" ;; ISO 8601 "Timestamp format used in `weechat-image-buffer'. See `format-time-string'." :type 'string :group 'weechat-image) (defun weechat-image--remove (button) "Remove image associated with BUTTON." (let ((start (button-get button 'weechat-image-begin)) (end (button-get button 'weechat-image-end))) (remove-images start end) (delete-region (1- (overlay-start button)) (overlay-end button)) (delete-overlay button) (save-excursion (save-restriction (narrow-to-region (line-beginning-position) (line-end-position)) (let ((inhibit-read-only t)) (weechat-image--add-preview-button)))))) (defun weechat-image-insert-inline (url image buffer marker) "Insert IMAGE after MARKER in buffer." (with-current-buffer buffer (goto-char marker) (let ((button (button-at marker))) (delete-region (overlay-start button) (overlay-end button)) (delete-overlay button)) (let ((button-start (point)) button-end image-start) (insert "[-]") (setq button-end (point)) (end-of-line) (setq image-start (point)) (put-image image image-start) (make-button button-start button-end 'action #'weechat-image--remove 'help-wecho "Remove Preview" 'follow-link t 'weechat-image-begin image-start 'weechat-image-end (point)))) (message "Inserted inline %s %s %s" url buffer marker)) (defun weechat-image-view-next () "Go to next image." (interactive) (search-forward "URL:" nil t)) (defun weechat-image-view-previous () "Go to previous image." (interactive) (search-backward "URL:" nil t)) (defun weechat-image-view-remove-entry () "Remove current entry." (interactive) (save-excursion (let ((beg (if (looking-at "^URL:") (point) (search-backward "URL:" nil t))) (end (progn (end-of-line) (search-forward "URL:" nil t)))) (if end (setq end (- end 4)) (setq end (point-max))) (let ((inhibit-read-only t)) (remove-images beg end) (delete-region beg end))))) (defun weechat-image-view-clear () "Clear image view buffer." (interactive) (when (and (called-interactively-p 'interactive) (y-or-n-p "Clear buffer? ")) (let ((inhibit-read-only t)) (remove-images (point-min) (point-max)) (erase-buffer)))) (defvar weechat-image-view-mode-map (let ((map (make-sparse-keymap))) (define-key map "p" #'weechat-image-view-previous) (define-key map "n" #'weechat-image-view-next) (define-key map "c" #'weechat-image-view-clear) (define-key map "k" #'weechat-image-view-remove-entry) map) "Keymap for `weechat-image-view-mode'.") (easy-menu-define weechat-image-view-mode-menu weechat-image-view-mode-map "WeeChat Image" '("WeeChatImage" ["Previous Image" weechat-image-view-previous t] ["Next Image" weechat-image-view-next t] ["Remove Image" weechat-image-view-remove-entry t] ["Clear Buffer" weechat-image-view-clear t])) (define-derived-mode weechat-image-view-mode special-mode "WeechatImage" "Mode for the weechat-image viewer \{weechat-image-view-mode-map}" :group 'weechat-image) (defun weechat-image-insert-other-buffer (url image buffer marker) "Insert IMAGE into `weechat-image-buffer'." (with-current-buffer (get-buffer-create weechat-image-buffer) (weechat-image-view-mode) (goto-char (point-max)) (let ((inhibit-read-only t)) (unless (bolp) (insert "\n")) (insert "URL: ") (insert-button url 'action (lambda (button) (browse-url (button-get button 'weechat-image-url))) 'help-echo "Browse URL" 'follow-link t 'weechat-image-url url) (insert "\n") (let ((channel-name (buffer-name buffer))) (insert "Channel: ") (insert-button channel-name 'action (lambda (button) (let ((buf (button-get button 'weechat-image-buffer)) (mark (button-get button 'weechat-image-marker))) (when (buffer-live-p buf) (switch-to-buffer buf) (with-current-buffer buf (goto-char mark))))) 'help-echo "Goto buffer" 'follow-link t 'weechat-image-buffer buffer 'weechat-image-marker marker) (insert "\n")) (let (nick date) (with-current-buffer buffer (goto-char marker) (beginning-of-line) (setq nick (get-text-property (point) 'weechat-nick)) (setq date (get-text-property (point) 'weechat-date))) (when date (insert "Date: " (format-time-string weechat-image-time-format date) "\n")) (when nick (insert "By: ") (insert-button nick 'action (lambda (button) (let ((buf (button-get button 'weechat-image-buffer)) (nick (button-get button 'weechat-image-nick))) (with-current-buffer buf (weechat-nick-action nick)))) 'help-echo "Nick Actions" 'follow-link t 'weechat-image-buffer buffer 'weechat-image-nick nick) (insert "\n"))) (put-image image (point)) (insert "\n"))) (message "Added new image to %s" weechat-image-buffer) (switch-to-buffer weechat-image-buffer)) (defun weechat-image-resize (image what px) "Resize IMAGE. WHAT should be either `:width' or `:height' and PX is the new size in pixel. This function is a no-op if `weechat-image-use-imagemagick' is nil." (if weechat-image-use-imagemagick (or (create-image (plist-get (cdr image) :data) 'imagemagick t what px) image) image)) (defun weechat-image--get-image (_status url buffer marker) (goto-char (point-min)) (unless (looking-at "^HTTP/.+ 200 OK$") (kill-buffer) (error "Error while fetching image `%s'" url)) (unless (search-forward "\n\n" nil t) (kill-buffer) (error "Error while fetching image `%s'. Malformed http reply" url)) (when (and weechat-image-size-limit (> (- (point-max) (point)) weechat-image-size-limit)) (kill-buffer) (error "Image %s is too large (%s bytes)" url (- (point-max) (point)))) (let* ((image (create-image (buffer-substring (point) (point-max)) (if weechat-image-use-imagemagick 'imagemagick nil) t)) (size (image-size image 'pixels))) (unless image (kill-buffer) (error "Image type not supported or not an image.")) (when (and weechat-image-max-width (> (car size) weechat-image-max-width)) (if weechat-image-resize (setq image (weechat-image-resize image :width weechat-image-max-width)) (kill-buffer) (error "Image %s is too wide (%s px)" url (car size)))) (when (and weechat-image-max-height (> (cdr size) weechat-image-max-height)) (if weechat-image-resize (setq image (weechat-image-resize image :height weechat-image-max-width)) (kill-buffer) (error "Image %s is too heigh (%s px)" url (cdr size)))) (kill-buffer) (funcall weechat-image-display-func url image buffer marker))) (defun weechat-image--do-preview (button) (let ((url (button-get button 'weechat-image-url)) (buffer (button-get button 'weechat-image-buffer)) (marker (button-get button 'weechat-image-marker))) (url-queue-retrieve url #'weechat-image--get-image (list url buffer marker)))) (defun weechat-image--add-preview-button () "Add preview buttons after image urls." (goto-char (point-min)) (search-forward "http" nil t) (let ((url (thing-at-point 'url))) (when (and url (s-matches? weechat-image-url-regex url) (not (s-matches? weechat-image-url-blacklist-regex url))) (end-of-thing 'url) (insert " ") (insert-button "[+]" 'action #'weechat-image--do-preview 'help-echo "Preview Image" 'follow-link t 'weechat-image-marker (point) 'weechat-image-buffer (current-buffer) 'weechat-image-url url) (unless (or (eolp) (looking-at "[[:space:]]")) (insert " "))))) (add-hook 'weechat-insert-modify-hook #'weechat-image--add-preview-button) (provide 'weechat-image) ;;; weechat-image.el ends here weechat.el-0.5.0/weechat-latex.el000066400000000000000000000144411327601011500166100ustar00rootroot00000000000000;;; weechat-latex --- Add LateX preview -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; The LaTeX preview is based on `org-mode's `org-format-latex' ;;; Code: (require 'weechat) (require 'org) (defgroup weechat-latex nil "WeeChat LaTeX preview." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-latex" :group 'weechat) (defcustom weechat-latex-temp-file-prefix "weechat-latex" "Prefix for temporary files." :type 'string :group 'weechat-latex) (defcustom weechat-latex-temp-directory-prefix "weechat-latex" "Prefix for temporary directory." :type 'string :group 'weechat-latex) (defcustom weechat-latex-image-program org-latex-create-formula-image-program "Program to convert LaTeX fragments. See `org-latex-create-formula-image-program'" :type '(choice (const :tag "dvipng" dvipng) (const :tag "imagemagick" imagemagick)) :group 'weechat-latex) (defvar weechat-latex-temp-dir nil "The temporary directory used for preview images.") (defun weechat-latex--create-preview (at) "Wrapper for `org-format-latex'. The parameter AT should be nil or in (TYPE . POINT) format. With TYPE being a string showing the matched LaTeX statement (e.g., ``$'') and POINT being the POINT to replace. If AT is nil replace statements everywhere." (org-format-latex weechat-latex-temp-file-prefix weechat-latex-temp-dir 'overlays "Creating images...%s" at 'forbuffer weechat-latex-image-program)) (defun weechat-latex--set-temp-dir () "Set `weechat-latex-temp-dir' unless it is already set." (unless weechat-latex-temp-dir (setq weechat-latex-temp-dir (make-temp-file weechat-latex-temp-directory-prefix 'directory)))) (defun weechat-latex-preview () "Preview LaTeX fragments." (interactive) (save-excursion (let ((inhibit-read-only t)) (weechat-latex--set-temp-dir) (org-remove-latex-fragment-image-overlays) (weechat-latex--create-preview nil)))) (defun weechat-latex-preview-region (beg end) "Preview LaTeX fragments in region." (interactive "r") (let* ((math-regex (assoc "$" org-latex-regexps)) (regex (nth 1 math-regex)) (n (nth 2 math-regex)) matches) (save-excursion (goto-char beg) (while (re-search-forward regex end t) (setq matches (cons (cons "$" (match-beginning n)) matches))) (let ((inhibit-read-only t)) (weechat-latex--set-temp-dir) (dolist (i matches) (weechat-latex--create-preview i)))))) (defvar weechat-prompt-start-marker) ;; See weechat.el (defun weechat-latex-preview-line () "Preview LaTeX fragments in line." (interactive) (weechat-latex-preview-region (point-at-bol) (min (point-at-eol) weechat-prompt-start-marker))) (defun weechat-latex-remove () "Remove LaTeX preview images." (interactive) (let ((inhibit-read-only t)) (org-remove-latex-fragment-image-overlays))) (defun weechat-latex-is-active? () "Are LaTeX Previews currently displayed?" org-latex-fragment-image-overlays) (defun weechat-latex-toggle () "Toggle display of LaTeX preview." (interactive) (if (weechat-latex-is-active?) (weechat-latex-remove) (weechat-latex-preview))) ;;; auto mode (defun weechat-latex--do-auto-mode () "Hook for auto LaTeX preview." (weechat-latex-preview-region (point-min) (point-max))) (defun weechat-latex-is-auto-mode-active? () "Is auto LaTeX preview active?" (memq #'weechat-latex--do-auto-mode weechat-insert-modify-hook)) (defcustom weechat-latex-auto-mode-line-string " LaTeX-Preview" "String displayed in mode line when `weechat-latex-auto-mode' is active." :type 'string :group 'weechat-latex) (defcustom weechat-latex-auto-mode-preview-all t "Show preview for existing LaTeX fragmetns if auto mode is activated?" :type 'boolean :group 'weechat-latex) (define-minor-mode weechat-latex-auto-mode "Automatically display LaTeX preview." :lighter weechat-latex-auto-mode-line-string :group 'weechat-latex (if weechat-latex-auto-mode (progn (when weechat-latex-auto-mode-preview-all (weechat-latex-preview)) (add-hook 'weechat-insert-modify-hook #'weechat-latex--do-auto-mode)) (remove-hook 'weechat-insert-modify-hook #'weechat-latex--do-auto-mode))) ;;; module (easy-menu-add-item weechat-mode-menu nil ["LaTeX Preview" weechat-latex-toggle :style toggle :selected (weechat-latex-is-active?) :help "If selected show LaTeX preview for existing buffer."] "Toggle Hidden Lines") (easy-menu-add-item weechat-mode-menu nil ["LaTeX Auto Preview" weechat-latex-auto-mode :style toggle :selected (weechat-latex-is-auto-mode-active?) :help "If selected automatically show LaTeX preview for new messages."] "Toggle Hidden Lines") (defun weechat-latex-unload-function () "Cleanup WeeChat LaTex module." (weechat-latex-auto-mode -1) (weechat-latex-remove) (easy-menu-remove-item weechat-mode-menu nil "LaTeX Preview") (easy-menu-remove-item weechat-mode-menu nil "LaTeX Auto Preview")) (provide 'weechat-latex) ;;; weechat-latex.el ends here weechat.el-0.5.0/weechat-notifications.el000066400000000000000000000110561327601011500203430ustar00rootroot00000000000000;;; weechat-notifications --- notifications.el based notifications. ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; Notifications based on notifications.el. Shipped with Emacs. ;;; Code: (require 'weechat) (require 'notifications) (require 'xml) ;; For `xml-escape-string' (defgroup weechat-notifications nil "Notifications based on notifications.el." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-notifications-" :group 'weechat) (defcustom weechat-notifications-icon #'weechat-notifications-icon-function "Icon used in notifications. Either nil, a file-name, or a function which is called with (SENDER BUFFER-PTR)." :type '(choice (const :tag "No icon" nil) (file :tag "Icon file") (function :tag "Icon function")) :group 'weechat-notifications) (defcustom weechat-notifications-sound t "Sound to use for notifications: - nil: No sound - t: default message-new-instant sound - string: file name of a sound file." :type '(choice (const :tag "No sound" nil) (const :tag "Default system sound" t) (file :tag "Sound file")) :group 'weechat-notifications) (defun weechat-notifications-icon-function (_sender _buffer-ptr) "Default icon." (when (boundp 'notifications-application-icon) notifications-application-icon)) (defvar weechat--notifications-id-to-msg nil "Map notification ids to buffer-ptrs.") (defun weechat--notifications-action (id key) "Handle notifcations.el actions. See `weechat-notifications-handler'. Supported actions: - read: switch to buffer." (when (string= key "view") (let* ((buffer-ptr (cdr (assoc id weechat--notifications-id-to-msg)))) (when buffer-ptr (weechat-switch-buffer buffer-ptr))))) (defun weechat-notifications-handler (type &optional sender text _date buffer-ptr) "Notification handler using notifications.el." (let ((notifications-id (notifications-notify :title (xml-escape-string (or (cl-case type (:highlight (concat "Weechat.el: Message from <" (weechat-strip-formatting sender) ">")) (:query (concat "Weechat.el: Query from <" (weechat-strip-formatting sender) ">")) (:disconnect "Disconnected from WeeChat")) "")) :body (when text (xml-escape-string text)) :category "im.received" :actions '("view" "View") :on-action #'weechat--notifications-action :app-icon (cl-typecase weechat-notifications-icon (string weechat-notifications-icon) (function (funcall weechat-notifications-icon sender buffer-ptr))) :app-name "WeeChat.el" :sound-name (when (and weechat-notifications-sound (not (stringp weechat-notifications-sound))) "message-new-instant") :sound-file (when (stringp weechat-notifications-sound) weechat-notifications-sound) :replaces-id (caar weechat--notifications-id-to-msg)))) (when notifications-id (setq weechat--notifications-id-to-msg (append (list (cons notifications-id buffer-ptr)) weechat--notifications-id-to-msg))))) (add-hook 'weechat-notification-handler-functions #'weechat-notifications-handler) (provide 'weechat-notifications) ;;; weechat-notifications.el ends here weechat.el-0.5.0/weechat-pkg.el.in000066400000000000000000000002261327601011500166550ustar00rootroot00000000000000(define-package "weechat" "@VERSION@" "Chat via WeeChat's relay protocol in Emacs" '((s "1.3.1") (cl-lib "0.2") (emacs "24") (tracking "1.2"))) weechat.el-0.5.0/weechat-read-marker.el000066400000000000000000000121031327601011500176560ustar00rootroot00000000000000;;; weechat-read-marker.el --- read marker for WeeChat -*- lexical-binding: t -*- ;; Copyright (C) 2015 Hans-Peter Deifel ;; Author: Hans-Peter Deifel ;; Created: 22 Jun 2015 ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;;; Draw a thin red line between the read and unread lines in a buffer. ;;; See `M-x customize-group weechat-read-marker' for configuration options. ;;; If anything goes wrong, the read marker can be manually reset with ;;; `weechat-read-marker-reset'. ;;; Code: (require 'weechat) (defgroup weechat-read-marker nil "Read marker for WeeChat" :prefix "weechat-read-marker" :group 'weechat) (defcustom weechat-read-marker-char ?─ "Character used to render the read marker." :type 'character :group 'weechat-read-marker) (defface weechat-read-marker '((t :foreground "brown")) "Face used to colorize the read marker." :group 'weechat-read-marker) (defcustom weechat-read-marker-always-show nil "Always show read marker, even if it is after last buffer line. A value of t means that the read marker is displayed directly over the prompt, if there are no unread lines. With nil, the marker is simply not displayed in this case." :type 'boolean :group 'weechat-read-marker) (defvar-local weechat-read-marker-overlay nil "The overlay used to draw the read marker. Will be nil initially and if the buffer has no unread lines when `weechat-read-marker-always-show` is not set.") (defvar-local weechat-read-marker-stale t "Whether the read marker position is outdated. This will be set if the buffer is visited, to indicate that the unread lines are now read.") (defun weechat-read-marker--set-overlay () "Update the `after-string' property on an already existing overlay." (let* ((width (- (window-width) 1)) (line (make-string width weechat-read-marker-char))) (overlay-put weechat-read-marker-overlay 'after-string (concat "\n" (propertize line 'face 'weechat-read-marker))))) (defun weechat-read-marker--move-overlay () "Update the read marker in the current buffer." (if weechat-read-marker-overlay (move-overlay weechat-read-marker-overlay (point-at-bol) (point-at-eol)) (setq weechat-read-marker-overlay (make-overlay (point-at-bol) (point-at-eol) nil t nil)) ;; Delete overlay if text is deleted. This is needed to get rid of the ;; overlay, when the buffer is reset. (overlay-put weechat-read-marker-overlay 'evaporate t)) (weechat-read-marker--set-overlay)) (defun weechat-read-marker-handle-visited () "Reset read marker after a buffer is being visited." (if weechat-read-marker-stale ;; we haven't had any new lines. Reset overlay (when weechat-read-marker-overlay (if weechat-read-marker-always-show (save-excursion (goto-char (point-max)) (forward-line -1) (weechat-read-marker--move-overlay)) (delete-overlay weechat-read-marker-overlay) (setq weechat-read-marker-overlay nil))) ;; otherwise, set the read marker to be stale (setq weechat-read-marker-stale t) ;; and also recompute the overlay string, since the window-width could have ;; changed (when weechat-read-marker-overlay (weechat-read-marker--set-overlay)))) (defun weechat-read-marker-handle-background () "Move read-marker in case it is stale." (when weechat-read-marker-stale (save-excursion (goto-char (point-max)) (unless (< (line-number-at-pos) 3) (forward-line -2) (weechat-read-marker--move-overlay) (setq weechat-read-marker-stale nil))))) (defun weechat-read-marker-reset () "Manually reset the read marker in the current buffer." (interactive "") (when weechat-read-marker-overlay ;; Always delete (and possibly) recreate overlay in case anything went wrong ;; and the users used this command to reset things. (delete-overlay weechat-read-marker-overlay) (if weechat-read-marker-always-show (save-excursion (goto-char (point-max)) (forward-line -1) (weechat-read-marker--set-overlay)) (setq weechat-read-marker-overlay nil))) (setq weechat-read-marker-stale t)) (add-hook 'weechat-buffer-background-message-hook 'weechat-read-marker-handle-background) (add-hook 'weechat-buffer-visited-hook 'weechat-read-marker-handle-visited) (provide 'weechat-read-marker) ;;; weechat-read-marker.el ends here weechat.el-0.5.0/weechat-relay.el000066400000000000000000000606071327601011500166140ustar00rootroot00000000000000;;; weechat-relay --- Implementation of Weechat's relay protocol ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich (moritz@tarn-vedra.de) ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; Keywords: irc chat network weechat ;; 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 . ;;; Commentary: ;; This file implements low-level code used by weechat.el to connect ;; to remote WeeChat instances using the relay protocol: ;; http://www.weechat.org/files/doc/devel/weechat_relay_protocol.en.html (require 'weechat-core) (require 'bindat) (require 'format-spec) (require 's) (require 'pp) (require 'cl-lib) (defcustom weechat-relay-buffer-name "*weechat-relay*" "Buffer holding the connection to the host weechat instance." :type 'string :group 'weechat-relay) (defcustom weechat-relay-message-function nil "Function to call when receiving a new weechat message." :type '(choice (const :tag "Off" nil) (function :tag "Callback Function")) :group 'weechat-relay) (defvar weechat-relay-ignored-message-ids '("_nicklist") "IDs to ignore.") (defcustom weechat-relay-disconnect-hook nil "Hook run when the relay disconnects. It will NOT run when the user disconnects via `weechat-disconnect'." :type 'hook :group 'weechat-relay) (defcustom weechat-relay-connect-hook nil "Hook run when the relay connects. Note: This DOES NOT mean the client can is already authenticated to the relay server." :type 'hook :group 'weechat-relay) (defcustom weechat-relay-ping-idle-seconds 60 "Idle time in seconds after weechat.el will ping the server." :type '(choice integer (const nil)) :group 'weechat-relay) ;;; Code: (defvar weechat--relay-id-callback-hash (make-hash-table :test 'equal) "Alist mapping from ids to functions. Incoming message-ids will be searched in this alist and the corresponding function will be called.") (defun weechat--relay-send-message (text &optional id) "Send message TEXT with optional ID. Trim TEXT prior to sending it." (let ((msg (concat (when id (format "(%s) " id)) (s-trim text) "\n"))) (weechat-relay-log (format "Sending msg: '%s'" (s-trim msg)) :debug) (let ((process (get-buffer-process weechat-relay-buffer-name))) (if process (send-string process msg) (weechat-warn "`get-buffer-process' returned nil for `weechat-relay-buffer-name'"))))) (defun weechat-relay-authenticate (password &optional compression) "Authenticate to weechat with PASSWORD. PASSWORD can be a string, a function or nil. If COMPRESSION is non-nil, enable compression on this connection. Currently unsupported." (cl-assert (or (null password) (stringp password) (functionp password))) (let ((pass (if (functionp password) (funcall password) password))) (when (stringp pass) (setq pass (if (s-blank? (s-trim pass)) nil (s-trim pass)))) (weechat--relay-send-message (concat "init " (s-join "," (cl-remove-if #'s-blank? (list (when pass (format "password=%s" (s-trim pass))) (format "compression=%s" (if compression "zlib" "off"))))) "\n")))) (defun weechat--relay-bindat-unsigned-to-signed (num bytes) "Convert an unsigned int NUM to signed int. NUM is in two-complement representation with BYTES bytes. Useful because bindat does not support signed numbers." (if (> num (- (expt 2 (- (* 8 bytes) 1)) 1)) (- num (expt 2 (* 8 bytes))) num)) (defun weechat--relay-unpack-int (data) "Unpack a four-byte signed integer from unibyte string DATA. Return the value and number of bytes consumed." (cl-values (weechat--relay-bindat-unsigned-to-signed (bindat-get-field (bindat-unpack '((val u32)) data) 'val) 4) 4)) (defconst weechat--relay-lon-spec '((len u8) (val str (eval (weechat--relay-bindat-unsigned-to-signed (bindat-get-field struct 'len) 1))))) (defun weechat--relay-unpack-lon (data) (let ((obj (bindat-unpack weechat--relay-lon-spec data))) (cl-values (string-to-number (decode-coding-string (bindat-get-field obj 'val) 'utf-8)) (bindat-length weechat--relay-lon-spec obj)))) (defun weechat--relay-unpack-chr (data) "Unpack a one byte char from unibyte string DATA. Returns value and bytes consumed." (cl-values (bindat-get-field (bindat-unpack '((val u8)) data) 'val) 1)) (defconst weechat--relay-str-spec '((len u32) (val str (eval (let ((len (weechat--relay-bindat-unsigned-to-signed (bindat-get-field struct 'len) 4))) ;; Hack for signed/unsigned problems (if (<= len 0) 0 len)))))) (defun weechat--relay-unpack-str (data) "Unpacks a weechat-relay-string from unibyte string DATA. Optional second return value contains length of parsed data." (let ((obj (bindat-unpack weechat--relay-str-spec data))) (cl-values (decode-coding-string (bindat-get-field obj 'val) 'utf-8) (bindat-length weechat--relay-str-spec obj)))) (defconst weechat--relay-buf-spec '((len u32) (val vec (eval (let ((len (weechat--relay-bindat-unsigned-to-signed (bindat-get-field struct 'len) 4))) ;; Hack for signed/unsigned problems (if (<= len 0) 0 len)))))) (defun weechat--relay-unpack-buf (data) (let ((obj (bindat-unpack weechat--relay-buf-spec data))) (cl-values (bindat-get-field obj 'val) (bindat-length weechat--relay-buf-spec obj)))) (defconst weechat--relay-ptr-spec '((len u8) (val str (eval (let ((len (weechat--relay-bindat-unsigned-to-signed (bindat-get-field struct 'len) 1))) ;; Hack for signed/unsigned problems (if (<= len 0) 0 len)))))) (defun weechat--relay-unpack-ptr (data) "Unpack a string encoded in weechat's binary representation. DATA must be an unibyte string. Return string-value and number of bytes consumed." (let ((obj (bindat-unpack weechat--relay-ptr-spec data))) (cl-values (unless (string= "0" (bindat-get-field obj 'val)) (concat "0x" (bindat-get-field obj 'val))) (bindat-length weechat--relay-ptr-spec obj)))) (defconst weechat--relay-tim-spec '((len u8) (val str (eval (let ((len (weechat--relay-bindat-unsigned-to-signed (bindat-get-field struct 'len) 1))) ;; Hack for signed/unsigned problems (if (<= len 0) 0 len)))))) (defun weechat--relay-unpack-tim (data) (let ((obj (bindat-unpack weechat--relay-tim-spec data))) (cl-values (let ((val (string-to-number (bindat-get-field obj 'val)))) (unless (zerop val) (seconds-to-time val))) (bindat-length weechat--relay-tim-spec obj)))) (defconst weechat--relay-htb-spec '((key-type str 3) (val-type str 3) (count u32))) (defun weechat--relay-unpack-htb (data) (let* ((obj (bindat-unpack weechat--relay-htb-spec data)) (count (weechat--relay-bindat-unsigned-to-signed (bindat-get-field obj 'count) 4)) (key-type (bindat-get-field obj 'key-type)) (val-type (bindat-get-field obj 'val-type)) (key-fn (symbol-function (intern (concat "weechat--relay-unpack-" key-type)))) (val-fn (symbol-function (intern (concat "weechat--relay-unpack-" val-type)))) (offset (bindat-length weechat--relay-htb-spec obj)) (acc ())) (dotimes (_ count) (cl-multiple-value-bind (key key-len) (funcall key-fn (substring data offset)) (cl-multiple-value-bind (val val-len) (funcall val-fn (substring data (+ offset key-len))) (setq acc (cons (cons key val) acc)) (setq offset (+ offset key-len val-len))))) (cl-values acc offset))) (defconst weechat--relay-arr-spec '((type str 3) (count u32))) (defun weechat--relay-unpack-arr (data) (let* ((obj (bindat-unpack weechat--relay-arr-spec data)) (count (weechat--relay-bindat-unsigned-to-signed (bindat-get-field obj 'count) 4)) (type (bindat-get-field obj 'type)) (unpack-fn (symbol-function (intern (concat "weechat--relay-unpack-" type)))) (offset (bindat-length weechat--relay-arr-spec obj)) (acc ())) (dotimes (_ count) (cl-multiple-value-bind (val val-len) (funcall unpack-fn (substring data offset)) (setq acc (append acc (list val))) (setq offset (+ offset val-len)))) (cl-values acc offset))) (defalias 'weechat--relay-parse-chr 'weechat--relay-unpack-chr) (defalias 'weechat--relay-parse-int 'weechat--relay-unpack-int) (defalias 'weechat--relay-parse-lon 'weechat--relay-unpack-lon) (defalias 'weechat--relay-parse-str 'weechat--relay-unpack-str) (defalias 'weechat--relay-parse-buf 'weechat--relay-unpack-buf) (defalias 'weechat--relay-parse-ptr 'weechat--relay-unpack-ptr) (defalias 'weechat--relay-parse-tim 'weechat--relay-unpack-tim) (defalias 'weechat--relay-parse-arr 'weechat--relay-unpack-arr) (defun weechat--relay-parse-inf (data) (cl-multiple-value-bind (name len) (weechat--relay-unpack-str data) (cl-multiple-value-bind (value len*) (weechat--relay-unpack-str (substring data len)) (cl-values (cons name value) (+ len len*))))) (defconst weechat--relay-inl-item-spec '((name struct weechat--relay-str-spec) (type str 3))) (defun weechat--relay-parse-inl-item (data) (let* ((count (weechat--relay-bindat-unsigned-to-signed (bindat-get-field (bindat-unpack '((len u32)) data) 'len) 4)) (offset 4) (acc ())) (while (< (length acc) count) (let* ((obj (bindat-unpack weechat--relay-inl-item-spec (substring data offset))) (fun (symbol-function (intern (concat "weechat--relay-unpack-" (bindat-get-field obj 'type)))))) (setq offset (+ offset (bindat-length weechat--relay-inl-item-spec obj))) (cl-multiple-value-bind (value offset*) (funcall fun (substring data offset)) (setq offset (+ offset offset*)) (setq acc (cons (cons (bindat-get-field obj 'name 'val) value) acc))))) (cl-values acc offset))) (defconst weechat--relay-inl-spec '((name struct weechat--relay-str-spec) (count u32))) (defun weechat--relay-parse-inl (data) (let* ((obj (bindat-unpack weechat--relay-inl-spec data)) (acc ()) (count (weechat--relay-bindat-unsigned-to-signed (bindat-get-field obj 'count) 4)) (offset (bindat-length weechat--relay-inl-spec obj))) (dotimes (_ count) (cl-multiple-value-bind (item offset*) (weechat--relay-parse-inl-item (substring data offset)) (setq acc (cons item acc)) (setq offset (+ offset offset*)))) (cl-values acc offset))) (defun weechat--relay-parse-hda-item (h-path-length name-type-alist data) (let ((p-path ()) (offset 0) (result ())) (dotimes (_ h-path-length) (cl-multiple-value-bind (el offset*) (weechat--relay-unpack-ptr (substring data offset)) (setq p-path (cons el p-path)) (setq offset (+ offset offset*)))) (dolist (name-type name-type-alist) (let ((fun (symbol-function (intern (concat "weechat--relay-unpack-" (cdr name-type)))))) (cl-multiple-value-bind (obj offset*) (funcall fun (substring data offset)) (setq result (cons (cons (car name-type) obj) result)) (setq offset (+ offset offset*))))) (cl-values (cons (reverse p-path) result) offset))) (defconst weechat--relay-hdh-spec '((h-path struct weechat--relay-str-spec) (keys struct weechat--relay-str-spec) (count u32))) ;;; from http://lists.gnu.org/archive/html/help-gnu-emacs/2009-06/msg00764.html (defun weechat--partition-list (list length) (cl-loop while list collect (cl-subseq list 0 length) do (setf list (nthcdr length list)))) (defun weechat--hda-split-keys-string (str) (mapcar (lambda (x) (cons (car x) (cadr x))) (weechat--partition-list (split-string str "[:,]") 2))) (defun weechat--relay-parse-hda (data) (let* ((obj (bindat-unpack weechat--relay-hdh-spec data)) (count (weechat--relay-bindat-unsigned-to-signed (bindat-get-field obj 'count) 4)) (name-type-alist (weechat--hda-split-keys-string (bindat-get-field obj 'keys 'val))) (h-path-length (length (split-string (bindat-get-field obj 'h-path 'val) "[/]"))) (offset (+ (bindat-length weechat--relay-hdh-spec obj))) (acc ())) (dotimes (_ count) (cl-multiple-value-bind (obj offset*) (weechat--relay-parse-hda-item h-path-length name-type-alist (substring data offset)) (setq acc (cons obj acc)) (setq offset (+ offset offset*)))) (let ((h-path (bindat-get-field obj 'h-path 'val))) (cl-values (list h-path acc) offset)))) (defconst weechat--relay-message-spec '((length u32) (compression u8) (id struct weechat--relay-str-spec) (data vec (eval (let ((l (- (bindat-get-field struct 'length) 4 ;length 1 ;compression (+ 4 (length (bindat-get-field struct 'id 'val)))))) l))))) (defun weechat--unpack-message-contents (data) (let* ((type (substring data 0 3)) (fun (symbol-function (intern (concat "weechat--relay-parse-" type))))) (cl-multiple-value-bind (obj len) (funcall fun (string-make-unibyte (substring data 3))) (cl-values obj (+ len 3))))) (defun weechat-unpack-message (message-data) "Unpack weechat relay message in MESSAGE-DATA. Return a list: (id data)." (let* ((msg (bindat-unpack weechat--relay-message-spec message-data)) (data (concat (bindat-get-field msg 'data))) (msg-id (bindat-get-field msg 'id 'val)) (ignore-msg (member msg-id weechat-relay-ignored-message-ids)) (offset 0) (acc ())) ;; Only no-compression is supported atm (unless (= 0 (bindat-get-field msg 'compression)) (error "Compression not supported")) (unless ignore-msg (while (< offset (length data)) (cl-multiple-value-bind (obj offset*) (weechat--unpack-message-contents (substring data offset)) (setq offset (+ offset offset*)) (setq acc (cons obj acc))))) (cl-values (cons msg-id (if ignore-msg '(ignored) (reverse acc))) (bindat-get-field msg 'length)))) (defun weechat--message-available-p () "Check if a weechat relay message available in current buffer." (and (> (buffer-size) 5) (>= (buffer-size) (bindat-get-field (bindat-unpack '((len u32)) (buffer-string)) 'len)))) (defun weechat--relay-parse-new-message () (when (weechat--message-available-p) (cl-multiple-value-bind (ret len) (weechat-unpack-message (buffer-string)) (weechat-relay-log (format "Consumed %d bytes" len) :debug) (let ((inhibit-read-only t)) (delete-region (point-min) (+ (point-min) len))) ret))) (defun weechat-relay-get-id-callback (id) (gethash id weechat--relay-id-callback-hash)) (defun weechat-relay-remove-id-callback (id) (let ((fun (weechat-relay-get-id-callback id))) (remhash id weechat--relay-id-callback-hash) fun)) (defun weechat-relay-add-id-callback (id function &optional one-shot force) (unless id (error "ID must not be nil")) (when (weechat-relay-get-id-callback id) (unless force (error "ID '%s' is already in `weechat--relay-id-callback-hash'" id)) (weechat-relay-remove-id-callback id)) (let ((function* (if one-shot (lambda (x) (funcall function x) (weechat-relay-remove-id-callback id)) function))) (puthash id function* weechat--relay-id-callback-hash))) (defun weechat-relay-send-command (command &optional callback) "Send COMMAND to relay and call CALLBACK with reply. CALLBACK takes one argument (the response data) which is a list." (let ((id (symbol-name (cl-gensym)))) (when (functionp callback) (weechat-relay-add-id-callback id callback 'one-shot)) (weechat--relay-send-message command id))) (defvar weechat-relay-last-receive nil "Stores the time when the last message was received.") (defun weechat--relay-send-ping (&optional pong-callback) (weechat-relay-send-command "info version" pong-callback)) ;;; TODO: Move this functionality to weechat.el (defvar weechat--relay-ping-timer nil) (defun weechat--relay-stop-ping-timer () (when (timerp weechat--relay-ping-timer) (cancel-timer weechat--relay-ping-timer) (setq weechat--relay-ping-timer nil))) (defun weechat--relay-start-ping-timer () (weechat--relay-stop-ping-timer) (setq weechat--relay-ping-timer (run-with-timer 0 (/ weechat-relay-ping-idle-seconds 2) (lambda () (if (weechat-relay-connected-p) (when (>= (float-time (time-since weechat-relay-last-receive)) weechat-relay-ping-idle-seconds) (weechat--relay-send-ping)) ;; Stop the ping timer if we aren't connected (weechat--relay-stop-ping-timer)))))) (defun weechat--relay-process-filter (proc string) (with-current-buffer (process-buffer proc) (weechat-relay-log (format "Received %d bytes" (length string)) :debug) ;; Insert the text, advancing the process marker. (goto-char (point-max)) (let ((inhibit-read-only t)) (insert (string-make-unibyte string))) (let ((inhibit-redisplay t)) (while (weechat--message-available-p) (let* ((data (weechat--relay-parse-new-message)) (id (weechat--message-id data))) (setq weechat-relay-last-receive (current-time)) ;; If buffer is available, log message. (when (eq weechat-relay-log-level :debug) (weechat-relay-log (pp-to-string data) :debug)) ;; Call `weechat-relay-message-function' (when (functionp weechat-relay-message-function) (funcall weechat-relay-message-function data)) ;; Call callback from `weechat--relay-id-callback-hash' (if (functionp (weechat-relay-get-id-callback id)) (funcall (weechat-relay-get-id-callback id) (weechat--message-data data)))))))) (defvar weechat--relay-connected-callback) (defun weechat--relay-handle-process-status (status) (weechat-relay-log (format "Received status event: %s\n" status)) (cl-case status ((closed exit) (run-hooks 'weechat-relay-disconnect-hook)) (failed (error "Failed to connect to weechat relay") (weechat-relay-disconnect)))) (defun weechat--relay-process-sentinel (proc _) (weechat--relay-handle-process-status (process-status proc))) (defun weechat--relay-open-socket (name buffer host service) (make-network-process :name name :buffer buffer :host host :service service :coding 'binary :keepalive t :noquery t)) (defun weechat--relay-plain-socket (bname host port) (weechat-relay-log (format "PLAIN %s:%d" host port) :info) (weechat--relay-open-socket "weechat-relay" bname host port)) (defun weechat--relay-tls-socket (bname host port) (weechat-relay-log (format "TLS %s:%d" host port) :info) (require 'gnutls) (open-network-stream "weechat-relay-tls" bname host port :type 'tls :coding 'binary)) (defun weechat--relay-from-command (cmdspec) (lambda (bname host port) (let ((cmd (format-spec cmdspec (format-spec-make ?h host ?p port)))) (weechat-relay-log (format "COMMAND %s:%s: `%s'" host port cmd)) (let ((process-connection-type nil)) ; Use a pipe. (start-process-shell-command "weechat-relay-cmd" bname cmd))))) (defun weechat-relay-connect (host port mode &optional callback) "Open a new weechat relay connection to HOST at PORT. Argument MODE 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. Optional argument CALLBACK Called after initialization is finished." ;; Clean relay buffer to start with clean state (with-current-buffer (get-buffer-create weechat-relay-buffer-name) (let ((inhibit-read-only t)) (delete-region (point-min) (point-max)))) (let* ((pfun (cond ((or (null mode) (eq mode 'plain)) #'weechat--relay-plain-socket) ((or (eq mode t) (eq mode 'ssl)) #'weechat--relay-tls-socket) ((stringp mode) (weechat--relay-from-command mode)))) (process (funcall pfun weechat-relay-buffer-name host port))) (set-process-sentinel process #'weechat--relay-process-sentinel) (set-process-coding-system process 'binary) (set-process-filter process #'weechat--relay-process-filter) (with-current-buffer (get-buffer weechat-relay-buffer-name) (setq buffer-read-only t) (set-buffer-multibyte nil) (buffer-disable-undo))) (with-current-buffer (get-buffer-create weechat-relay-log-buffer-name) (buffer-disable-undo)) (when (functionp callback) (funcall callback)) (run-hooks 'weechat-relay-connect-hook)) (defun weechat-relay-connected-p () (and (get-buffer weechat-relay-buffer-name) (get-buffer-process weechat-relay-buffer-name) (process-live-p (get-buffer-process weechat-relay-buffer-name)) t)) (defun weechat-relay-disconnect () "Disconnect current weechat relay connection and close all buffers." (when (weechat-relay-connected-p) (weechat--relay-send-message "quit") (with-current-buffer weechat-relay-buffer-name (let ((proc (get-buffer-process (current-buffer)))) ;; When disconnecting interactively (e.g. when this function ;; is called), prevent running any disconnect hooks. (set-process-sentinel proc nil) (delete-process proc) ;; Stop the ping timer (weechat--relay-stop-ping-timer)) (kill-buffer)) (when (get-buffer weechat-relay-log-buffer-name) (kill-buffer weechat-relay-log-buffer-name)))) (defun weechat--message-id (message) (car message)) (defun weechat--message-data (message) "Return a list with data in MESSAGE." (cdr message)) (defun weechat--hdata-path (hdata) (car hdata)) (defun weechat--hdata-values (hdata) (cadr hdata)) (defun weechat--hdata-value-pointer-path (value) (car value)) (defun weechat--hdata-value-alist (value) (cdr value)) (defun weechat-unload-function () (ad-disable-advice 'open-gnutls-stream 'around 'weechat-verifying)) (provide 'weechat-relay) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; weechat-relay.el ends here weechat.el-0.5.0/weechat-sauron.el000066400000000000000000000051471327601011500170050ustar00rootroot00000000000000;;; weechat-sauron --- Sauron Notifications ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; Notifications using Sauron https://github.com/djcb/sauron ;;; Code: (require 'weechat) (if (require 'sauron nil 'noerr) (progn (defun weechat-sauron-handler (type &optional sender text _date buffer-ptr) (setq text (if text (weechat-strip-formatting text))) (setq sender (if sender (weechat-strip-formatting sender))) (let ((jump-position (point-max-marker))) (sauron-add-event 'weechat 3 (cl-case type (:highlight (format "%s in %s: %S" sender (weechat-buffer-name buffer-ptr) text)) (:query (format "Query from %s: %S" sender text)) (:disconnect "Disconnected from WeeChat")) (lambda () (when (fboundp 'sauron-switch-to-marker-or-buffer) (sauron-switch-to-marker-or-buffer jump-position))) ;; Flood protection based on sender (when sender (list :sender sender))))) (add-hook 'weechat-notification-handler-functions 'weechat-sauron-handler)) (weechat-warn "Error while loading weechat-sauron. Sauron notifications are disabled.")) (provide 'weechat-sauron) ;;; weechat-sauron.el ends here weechat.el-0.5.0/weechat-secrets.el000066400000000000000000000067411327601011500171470ustar00rootroot00000000000000;;; weechat --- Support secrets.el -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; secrets.el support for weechat.el ;; Load this file and then set `weechat-password-callback' to ;; `weechat-secrets-get-password'. You can use ;; `weechat-secrets-create' to create new entries and ;; `weechat-secrets-delete' to delete them. ;;; Code: (require 'weechat) (require 'secrets) (defgroup weechat-secrets nil "Secrets.el support for WeeChat." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-secrets" :group 'weechat) (defcustom weechat-secrets-collection "weechat.el" "Collection name." :type 'string :group 'weechat-secrets) (defun weechat-secrets--to-item (host port) "Convert HOST and PORT to an item name." (format "%s:%s" host port)) (defun weechat-secrets-create (host port &optional password) "Associate HOST and PORT with PASSWORD. A collection named after `weechat-secrets-collection' is created if required." (interactive (list (read-string (format "Host for password (default '%s'): " weechat-host-default) nil nil weechat-host-default) (read-number "Port for password: " weechat-port-default))) (unless secrets-enabled (error "Secrets.el-API not available.")) (unless (member weechat-secrets-collection (secrets-list-collections)) (secrets-create-collection weechat-secrets-collection)) (unless password (setq password (read-passwd "Password: " 'confirm))) (secrets-create-item weechat-secrets-collection (weechat-secrets--to-item host port) password :host host :port (number-to-string port)) (clear-string password)) (defun weechat-secrets-delete (host port &optional allow-empty-collection) "Delete HOST and PORT entry. Unless ALLOW-EMPTY-COLLECTION is non-nil then the collection is removed if it is empty." (interactive (list (read-string (format "Host for password (default '%s'): " weechat-host-default) nil nil weechat-host-default) (read-number "Port for password: " weechat-port-default))) (let ((item (weechat-secrets--to-item host port))) (secrets-delete-item weechat-secrets-collection item) (unless (and allow-empty-collection (null (secrets-list-items weechat-secrets-collection))) (secrets-delete-collection weechat-secrets-collection)))) (defun weechat-secrets-get-password (host port) "Get password for HOST and PORT." (secrets-get-secret weechat-secrets-collection (weechat-secrets--to-item host port))) (provide 'weechat-secrets) ;;; weechat-secrets.el ends here weechat.el-0.5.0/weechat-smiley.el000066400000000000000000000052531327601011500167760ustar00rootroot00000000000000;;; weechat-smiley --- Display smiley faces ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;; This module uses `smiley-region' from Gnus smiley.el. ;; To support short smileys (without nose) use: ;; ;; (setq smiley-regexp-alist ;; '(("\\(;-?)\\)\\W" 1 "blink") ;; ("[^;]\\(;)\\)\\W" 1 "blink") ;; ("\\(:-?]\\)\\W" 1 "forced") ;; ("\\(8-?)\\)\\W" 1 "braindamaged") ;; ("\\(:-?|\\)\\W" 1 "indifferent") ;; ("\\(:-?[/\\]\\)\\W" 1 "wry") ;; ("\\(:-?(\\)\\W" 1 "sad") ;; ("\\(X-?)\\)\\W" 1 "dead") ;; ("\\(:-?{\\)\\W" 1 "frown") ;; ("\\(>:-?)\\)\\W" 1 "evil") ;; ("\\(;-?(\\)\\W" 1 "cry") ;; ("\\(:-?D\\)\\W" 1 "grin") ;; ;; "smile" must be come after "evil" ;; ("\\(\\^?:-?)\\)\\W" 1 "smile"))) ;; It might be necessary to run ;; ;; (setq smiley-cached-regexp-alist nil) ;; (smiley-update-cache) ;; ;; To make it work when smiley is already loaded. ;;; Code: (require 'smiley) (require 'weechat) (defun weechat-smiley-buffer () "Smiley the region." (let ((inhibit-read-only t)) (smiley-buffer))) (defun weechat-smiley--do () "Hook for weechat." (save-excursion (let ((inhibit-read-only t)) (smiley-region (+ (point-min) weechat-text-column) (point-max))))) (weechat-do-buffers (weechat-smiley-buffer)) (add-hook 'weechat-insert-modify-hook #'weechat-smiley--do) (defun weechat-smiley-unload-function () "Remove smileys from buffer." (save-restriction (widen) (let ((inhibit-read-only t)) (weechat-do-buffers ;; smiley-toggle-buffer is broken somewhere in ;; `gnus-with-article-buffer'. Remove smileys directly. (smiley-region (point-min) (point-max)) (gnus-delete-images 'smiley))))) (provide 'weechat-smiley) ;;; weechat-smiley.el ends here weechat.el-0.5.0/weechat-speedbar.el000066400000000000000000000233131327601011500172560ustar00rootroot00000000000000;;; weechat --- Chat via Weechat's relay protocol in Emacs ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Keywords: irc ;; URL: https://github.com/the-kenny/weechat.el ;; This file is NOT part of GNU Emacs. ;; 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 . ;;; Commentary: ;; This code is inspired by erc-speedbar.el ;;; Code: (require 'weechat) (require 'speedbar) (defvar weechat-speedbar-key-map (let ((map (speedbar-make-specialized-keymap))) (define-key map "e" 'speedbar-edit-line) (define-key map "\C-m" 'speedbar-edit-line) (define-key map "+" 'speedbar-expand-line) (define-key map "=" 'speedbar-expand-line) (define-key map "-" 'speedbar-contract-line) map) "Weechat Speedbar Key Map") (defvar weechat-speedbar-menu-items '(["Go to buffer" speedbar-edit-line t] ["Expand Node" speedbat-expand-line (save-excursion (beginning-of-line) (looking-at "[0-9]+: *.\\+. "))] ["Contract Node" speedbar-contract-line (save-excursion (beginning-of-line) (looking-at "[0-9]+: *.-. "))]) "Weechat Speedbar Menu Items") (defun weechat-speedbar--goto-buffer (_text buffer _indent) (let ((buf (get-buffer buffer))) (speedbar-set-timer dframe-update-speed) (if (buffer-live-p buf) (switch-to-buffer buf) (when (y-or-n-p (format "Monitor buffer '%s'? " buffer)) (weechat-monitor-buffer (weechat--find-buffer buffer) 'show))))) (defvar weechat--buffer-hashes) ;; See weechat.el (defun weechat-speedbar--buttons (_directory depth) "Create buttons for speedbar in BUFFER." (erase-buffer) (maphash #'(lambda (_k v) (let* ((local-vars (gethash "local_variables" v)) (type (cdr (assoc-string "type" local-vars))) (name (cdr (assoc-string "name" local-vars))) (server (cdr (assoc-string "server" local-vars)))) (when (string= type "server") (speedbar-with-writable (speedbar-make-tag-line 'bracket ?+ #'weechat-speedbar--expand-server server server #'weechat-speedbar--goto-buffer name nil depth))))) weechat--buffer-hashes)) (defun weechat-speedbar--expand-server (text server indent) (cond ((string-match "+" text) ; expand node (speedbar-change-expand-button-char ?-) (speedbar-reset-scanners) (speedbar-with-writable (save-excursion (forward-line 1) (weechat-speedbar--channel-buttons nil (1+ indent) server)))) ((string-match "-" text) ; contract node (speedbar-change-expand-button-char ?+) (speedbar-delete-subblock indent)) (t (error "Not sure what do do!"))) (speedbar-center-buffer-smartly)) (defun weechat-speedbar--channel-buttons (_directory depth for-server) (maphash #'(lambda (_k v) (let* ((local-vars (gethash "local_variables" v)) (type (cdr (assoc-string "type" local-vars))) (name (cdr (assoc-string "name" local-vars))) (channel (cdr (assoc-string "channel" local-vars))) (server (cdr (assoc-string "server" local-vars)))) (when (string= server for-server) (cond ((string= type "channel") (speedbar-with-writable (speedbar-make-tag-line 'bracket ?+ #'weechat-speedbar--expand-channel name channel #'weechat-speedbar--goto-buffer name nil depth))) ((string= type "private") (speedbar-with-writable (speedbar-with-writable (speedbar-make-tag-line ;; TODO org-contacts/gravatar/bbdb? nil nil nil nil ;; TODO expand for whois? channel #'weechat-speedbar--goto-buffer name nil depth)))))))) weechat--buffer-hashes)) (defun weechat-speedbar--expand-channel (text name indent) (cond ((string-match "+" text) ; expand node (speedbar-change-expand-button-char ?-) (speedbar-reset-scanners) (speedbar-with-writable (save-excursion (forward-line 1) (weechat-speedbar--user-buttons nil (1+ indent) name)))) ((string-match "-" text) ; contract node (speedbar-change-expand-button-char ?+) (speedbar-delete-subblock indent)) (t (error "Not sure what do do!"))) (speedbar-center-buffer-smartly)) (defvar weechat-user-list) ;; See weechat.el (defun weechat-speedbar--user-buttons (_directory depth name) (let ((buffer (get-buffer name)) nick-list topic) (if (not (buffer-live-p buffer)) (speedbar-with-writable (speedbar-make-tag-line 'angle ?i nil nil "Not Monitored!" #'weechat-speedbar--goto-buffer name nil depth)) (with-current-buffer buffer (setq nick-list weechat-user-list) (setq topic weechat-topic)) (when topic (speedbar-with-writable (speedbar-make-tag-line 'angle ?i nil nil (concat "Topic: " topic) nil nil nil depth))) (dolist (nick nick-list) (speedbar-with-writable (speedbar-make-tag-line ;; TODO org-contacts/gravatar/bbdb? nil nil nil nil ;; TODO expand for whois? nick #'weechat-speedbar--user-action nick nil depth)))))) (defun weechat-speedbar--user-action (_text nick _indent) (weechat-nick-action nick)) (defun weechat-speedbar-item-info () "Display information about the current line." (let ((data (speedbar-line-token))) (message "item-info: %s" data))) (defvar weechat-speedbar--to-do-point t "Local variable maintaining the current modified check position.") (defcustom weechat-speedbar-highlight-indicator "" "Indicator used for highlight events." :type 'string :group 'weechat-speedbar) (defcustom weechat-speedbar-modified-indicator "" "Indicator used for normal message events." :type 'string :group 'weechat-speedbar) (defun weechat-speedbar--add-indicator (indicator) "Add INDICATOR to current line. This is similar to `speedbar-add-indicator' but supports weechat-speedbar indicators." (save-excursion (beginning-of-line) (end-of-line) (when (re-search-backward (concat "\\(" (regexp-quote weechat-speedbar-highlight-indicator) "\\|" (regexp-quote weechat-speedbar-modified-indicator) "\\)+") (line-beginning-position) t) (delete-region (match-beginning 0) (match-end 0))) (end-of-line) (when (not (string= " " indicator)) (let ((start (point))) (speedbar-with-writable (insert indicator) (speedbar-insert-image-button-maybe start (length indicator))))))) (defun weechat-speedbar--check-modified () "Scan all the channels and check if they are modified." (save-excursion (when speedbar-buffer (set-buffer speedbar-buffer)) (when (eq weechat-speedbar--to-do-point t) (setq weechat-speedbar--to-do-point 0)) (if (not (numberp weechat-speedbar--to-do-point)) t (goto-char weechat-speedbar--to-do-point) (while (and (not (input-pending-p)) (re-search-forward "^1: \\(\\[[+-]\\]\\|>\\) " nil t)) (setq weechat-speedbar--to-do-point (point)) (let* ((buffer-name (get-text-property (point) 'speedbar-token)) (buffer-ptr (weechat--find-buffer buffer-name))) (weechat-speedbar--add-indicator (if buffer-ptr (let ((hash (weechat-buffer-hash buffer-ptr))) (cond ((gethash :background-highlight hash) weechat-speedbar-highlight-indicator) ((gethash :background-message hash) weechat-speedbar-modified-indicator) (t " "))) " ")))) (if (input-pending-p) nil ; we are incomplete (setq weechat-speedbar--to-do-point nil) ; done t)))) (defun weechat-speedbar-install-variables () "Install WeeChat speedbar variables." (speedbar-add-expansion-list '("WeeChat" weechat-speedbar-menu-items weechat-speedbar-key-map weechat-speedbar--buttons)) (add-to-list 'speedbar-stealthy-function-list '("WeeChat" weechat-speedbar--check-modified)) (add-hook 'speedbar-scanner-reset-hook (lambda () (setq weechat-speedbar--to-do-point t))) (speedbar-add-mode-functions-list '("WeeChat" (speedbar-item-info . weechat-speedbar-item-info)))) (if (featurep 'speedbar) (weechat-speedbar-install-variables) (add-hook 'speedbar-load-hook #'weechat-speedbar-install-variables)) (defun weechat-speedbar-unload-function () ;; TODO ) (provide 'weechat-speedbar) ;;; weechat-speedbar.el ends here weechat.el-0.5.0/weechat-spelling.el000066400000000000000000000061731327601011500173130ustar00rootroot00000000000000;;; weechat-spelling.el --- FlySpell support for WeeChat. -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Rüdiger Sonderfeld ;; Moritz Ulrich ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; ;;; Code: (require 'weechat) (require 'flyspell) (require 's) (defgroup weechat-spelling nil "FlySpell support for WeeChat." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-spelling" :group 'weechat) (defcustom weechat-spelling-dictionaries nil "An alist mapping buffer names to dictionaries. The format of each entry is (CHANNEL . DICTIONARY). Where CHANNEL is a regexp matching the buffer name (\"Server.Channel\") and DICTIONARY is the name of an `ispell' dictionary. See `ispell-valid-dictionary-list' for a list of valid dictionaries." :type '(choice (const :tag "Default dictionary" nil) (repeat (cons (string :tag "Server.Channel Regex") (string :tag "Dictionary")))) :group 'weechat-spelling) (defun weechat-spelling-init (&optional buffer) "Initialize spelling in BUFFER or `current-buffer'." (with-current-buffer (or buffer (current-buffer)) (dolist (entry weechat-spelling-dictionaries) (when (s-matches? (car entry) (buffer-name)) (setq ispell-local-dictionary (cdr entry)))) (setq flyspell-generic-check-word-predicate #'weechat-mode-flyspell-verify) (flyspell-mode 1))) (defvar weechat-prompt-end-marker) ;; See weechat.el (defvar weechat-user-list) ;; See weechat.el (defun weechat-mode-flyspell-verify () "Function used for `flyspell-generic-check-word-predicate' in `weechat-mode'." (not (or ;; Spell-check only input line (< (point) weechat-prompt-end-marker) (let ((word-data (flyspell-get-word))) (or ;; Don't spell-check nick names (member (car word-data) weechat-user-list) ;; Don't spell-check words starting with a / (eq (char-before (cadr word-data)) ?/)))))) (put 'weechat-mode 'flyspell-mode-predicate #'weechat-mode-flyspell-verify) (weechat-do-buffers (weechat-spelling-init)) (add-hook 'weechat-mode-hook #'weechat-spelling-init) (defun weechat-spelling-unload-function () (weechat-do-buffers (flyspell-mode -1)) nil) (provide 'weechat-spelling) ;;; weechat-spelling.el ends here weechat.el-0.5.0/weechat-test.el000066400000000000000000000216471327601011500164600ustar00rootroot00000000000000(require 'weechat) (require 'ert) (require 'cl-lib) ;;; weechat-relay.el (defun weechat-test-callback-value (command) "Execute COMMAND and return the server response in a synchronous fashion." (let ((id (symbol-name (cl-gensym "id"))) (limit-sym 200) data-sym) (weechat-relay-add-id-callback id (lambda (d) (setq data-sym d)) 'one-shot) (weechat--relay-send-message command id) (while (and (> limit-sym 0) (not data-sym)) (sleep-for 0 50) (setq limit-sym (1- limit-sym))) data-sym)) (ert-deftest weechat-relay-id-callback () (let ((weechat--relay-id-callback-hash (copy-hash-table weechat--relay-id-callback-hash))) (let ((fun (lambda (_) nil)) ) (weechat-relay-add-id-callback "23" fun) (should (equal fun (weechat-relay-get-id-callback "23"))) (should (equal fun (weechat-relay-remove-id-callback "23")))) (clrhash weechat--relay-id-callback-hash) (should-error (progn (weechat-relay-add-id-callback "42" (lambda ())) (weechat-relay-add-id-callback "42" (lambda ())))))) (ert-deftest weechat-relay-id-callback-one-shot () (let ((weechat--relay-id-callback-hash (copy-hash-table weechat--relay-id-callback-hash))) (let ((fun (lambda (_) nil))) (weechat-relay-add-id-callback "23" fun 'one-shot) (funcall (weechat-relay-get-id-callback "23") nil) (should (equal nil (weechat-relay-get-id-callback "23")))))) (ert-deftest weechat-test-message-fns () (let ((message '("42" ("version" . "0.3.8")))) (should (equal "42" (weechat--message-id message))) (should (equal '("version" . "0.3.8") (car (weechat--message-data message)))))) (ert-deftest weechat-test-hdata-fns () (let ((hdata '("foo/bar" ((("0x155f870" "0xffffff") ("title" . "IRC: irc.euirc.net/6667 (83.137.41.33)") ("short_name" . "euirc") ("name" . "server.euirc")) (("0x1502940") ("title" . "IRC: irc.freenode.net/6697 (174.143.119.91)") ("short_name" . "freenode") ("name" . "server.freenode")))))) (should (equal "foo/bar" (weechat--hdata-path hdata))) (should (listp (weechat--hdata-values hdata))) (should (equal '(("0x155f870" "0xffffff") ("0x1502940")) (mapcar #'weechat--hdata-value-pointer-path (weechat--hdata-values hdata)))) (should (equal '((("title" . "IRC: irc.euirc.net/6667 (83.137.41.33)") ("short_name" . "euirc") ("name" . "server.euirc")) (("title" . "IRC: irc.freenode.net/6697 (174.143.119.91)") ("short_name" . "freenode") ("name" . "server.freenode"))) (mapcar #'weechat--hdata-value-alist (weechat--hdata-values hdata)))))) (ert-deftest weechat-test-infolist () (with-temp-buffer (set-buffer-multibyte nil) (insert (concat [0 0 0 32 0 255 255 255 255 105 110 102 0 0 0 7 118 101 114 115 105 111 110 0 0 0 5 48 46 51 46 56])) (let ((data (weechat--relay-parse-new-message (current-buffer)))) (should (equal "" (weechat--message-id data))) (should (equal '("version" . "0.3.8") (car (weechat--message-data data))))))) (ert-deftest weechat-test-id () (with-temp-buffer (set-buffer-multibyte nil) (insert (concat [0 0 0 35 0 0 0 0 3 54 54 54 105 110 102 0 0 0 7 118 101 114 115 105 111 110 0 0 0 5 48 46 51 46 56])) (let ((data (weechat--relay-parse-new-message (current-buffer)))) (should (equal "666" (weechat--message-id data))) (should (equal '("version" . "0.3.8") (car (weechat--message-data data))))))) (ert-deftest weechat-relay-test-connection () (when (weechat-relay-connected-p) (let ((version-resp (weechat-test-callback-value "info version"))) (should (equal "version" (caar version-resp))) (should (equal weechat-version (cdar version-resp)))))) (ert-deftest weechat-relay-test-test-command () (when (weechat-relay-connected-p) (let ((data (weechat-test-callback-value "test")) (i -1) (weechat-041 (list ?A 123456 -123456 1234567890 -1234567890 "a string" "" "" (string-to-vector "buffer") [] "0x1234abcd" nil (seconds-to-time 1321993456) (list "abc" "de") (list 123 456 789)))) (cl-flet ((next-val () (nth (setq i (1+ i)) data))) (cl-dolist (v (cond ((string= "0.4.1" weechat-version) weechat-041) (t (ert-fail (concat "No data for weechat-" weechat-version))))) (should (equal v (next-val)))))))) ;;; weechat.el (ert-deftest weechat-test-buffer-store () (let ((weechat--buffer-hashes (copy-hash-table weechat--buffer-hashes))) (weechat--clear-buffer-store) (should (eql 0 (hash-table-count weechat--buffer-hashes))) (let ((data '(("name" . "Foobar")))) (weechat--store-buffer-hash "0xffffff" data) (should (eq (cdar data) (gethash "name" (weechat-buffer-hash "0xffffff"))))) (weechat--remove-buffer-hash "0xffffff") (should (not (weechat-buffer-hash "0xffffff"))))) (ert-deftest weechat-color-stripping () (should (equal (weechat-strip-formatting "F14someone282728F05 has joined 13#asdfasdfasdfF05") "someone has joined #asdfasdfasdf")) (should (equal (weechat-strip-formatting "ddd") "ddd"))) (defun weechat-test--property-list (str &optional prop pos) "Return a list of property PROP in STR starting at POS. Default property is `face'. The returned format is ((START END (PROP VALUE)))." (setq pos (or pos 0)) (setq prop (or prop 'face)) (let ((next-pos pos) result) (while pos (setq next-pos (next-single-property-change pos prop str)) (setq result (cons (list pos (or next-pos (length str)) (list prop (get-text-property pos prop str))) result)) (setq pos next-pos)) result)) (ert-deftest weechat-color-handling () "Test `weechat-handle-color-codes'." (should (string= (weechat-handle-color-codes "foo bar baz") "foo bar baz")) (should (string= (weechat-handle-color-codes "\x19\F*02hi\x1C \x19\F/04world") "hi world")) (should (equal (weechat-test--property-list (weechat-handle-color-codes "\x19\F*02hi\x1C \x19\F/04world")) `((3 8 (face ((:foreground ,(nth 4 weechat-color-list)) (:slant italic)))) (2 3 (face default)) (0 2 (face ((:foreground ,(nth 2 weechat-color-list)) (:weight bold))))))) (should (string= (weechat-handle-color-codes "\x19\Fkaputt") "kaputt")) (should (string= (weechat-handle-color-codes "XY\x1A\Z") "XYZ")) (should (string= (weechat-handle-color-codes "\x1Bx") "x"))) (ert-deftest weechat-alist-merging () (should (equal '((x . 42)) (weechat-merge-alists '((x . 23)) '((x . 42))))) (should (equal '(("x" . 42)) (weechat-merge-alists '(("x" . 23)) '(("x" . 42))))) (should (equal '((x . 42)) (weechat-merge-alists '() '((x . 42))))) (should (equal '((x . 42)) (weechat-merge-alists '((x . 42)) '())))) (ert-deftest weechat-user-list () (let ((weechat-user-list)) (weechat--user-list-add "test") (should (equal weechat-user-list '("test"))) (weechat--user-list-add "test") (should (equal weechat-user-list '("test"))) (weechat--user-list-remove "notthere") (should (equal weechat-user-list '("test"))) (weechat--user-list-add "test2") (should (equal weechat-user-list '("test2" "test"))) (weechat--user-list-add "test_") (should (equal weechat-user-list '("test_" "test2" "test"))) (weechat--user-list-remove "test2") (should (equal weechat-user-list '("test_" "test"))) (weechat--user-list-remove "test") (should (equal weechat-user-list '("test_"))) (weechat--user-list-remove "test_") (should (eq weechat-user-list nil)) (weechat--user-list-add "") (should (eq weechat-user-list nil)) (weechat--user-list-add "x") (should (equal weechat-user-list '("x"))) (weechat--user-list-add "") (should (equal weechat-user-list '("x"))))) (require 'weechat-complete) (ert-deftest weechat-pcomplete-return-nil () (with-temp-buffer (should (eq (weechat-pcompletions-at-point) nil)))) weechat.el-0.5.0/weechat-tracking.el000066400000000000000000000073041327601011500172750ustar00rootroot00000000000000;;; weechat-tracking --- Tracking support for weechat.el ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Rüdiger Sonderfeld ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; Commentary: ;; This module provides tracking support, similar to erc-track, for weechat.el. ;; It requires the tracking library ;; https://github.com/jorgenschaefer/circe/wiki/Tracking ;; It should be available from marmalade or el-get. ;;; Code: (require 'tracking) (require 'cl-lib) (require 'weechat) (defgroup weechat-tracking nil "Tracking support for Weechat.el." :link '(url-link "https://github.com/the-kenny/weechat.el") :prefix "weechat-tracking-" :group 'weechat) (defcustom weechat-tracking-types '(:highlight) "A list of message types which should show up in tracking. List elements can either be one of :highlight or :message, or a cons-cell like (regex . level). The former will be applied to all buffers while the latter will apply to all buffers whose namesn matches `regex'. Supported values are :message and :highlight." :type '(repeat (choice symbol (cons string symbol))) :group 'weechat-tracking) (defcustom weechat-tracking-faces-priorities '(weechat-highlight-face) "A list of faces which should show up in the tracking. The first face is kept if the new message has only lower faces, or faces that don't show up at all." :type '(repeat face) :group 'weechat-tracking) (defun weechat-tracking-setup () "Set up tracking in weechat buffer." (set (make-local-variable 'tracking-faces-priorities) weechat-tracking-faces-priorities)) (defun weechat-tracking-show-buffer? (message-type &optional buffer) (or (cl-find message-type weechat-tracking-types) (cl-some (lambda (c) (and (consp c) (s-matches? (car c) (buffer-name buffer)) (eq message-type (cdr c)))) weechat-tracking-types))) (defun weechat-tracking-handle-highlight () (when (weechat-tracking-show-buffer? :highlight (current-buffer)) (tracking-add-buffer (current-buffer) '(weechat-highlight-face)))) (defun weechat-tracking-handle-message () (when (weechat-tracking-show-buffer? :message (current-buffer)) (tracking-add-buffer (current-buffer)))) (defun weechat-tracking-handle-reset () (tracking-remove-buffer (current-buffer))) (defun weechat-tracking-clear-buffers () (interactive) (mapcar #'tracking-remove-buffer (weechat-buffer-list))) (weechat-do-buffers (weechat-tracking-setup)) (add-hook 'weechat-mode-hook #'weechat-tracking-setup) (add-hook 'weechat-buffer-background-message-hook 'weechat-tracking-handle-message) (add-hook 'weechat-buffer-background-highlight-hook 'weechat-tracking-handle-highlight) (add-hook 'weechat-buffer-visited-hook 'weechat-tracking-handle-reset) (tracking-mode 1) (provide 'weechat-tracking) ;;; weechat-tracking.el ends here weechat.el-0.5.0/weechat.el000066400000000000000000002115601327601011500154760ustar00rootroot00000000000000;;; weechat --- Chat via WeeChat's relay protocol in Emacs ;; -*- lexical-binding: t -*- ;; Copyright (C) 2013 Moritz Ulrich ;; Author: Moritz Ulrich ;; Rüdiger Sonderfeld ;; Aristid Breitkreuz ;; 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 . ;;; 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 (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