commit | author | age
|
76bbd0
|
1 |
;;; org-irc.el --- Store Links to IRC Sessions -*- lexical-binding: t; -*- |
C |
2 |
;; |
|
3 |
;; Copyright (C) 2008-2018 Free Software Foundation, Inc. |
|
4 |
;; |
|
5 |
;; Author: Philip Jackson <emacs@shellarchive.co.uk> |
|
6 |
;; Keywords: erc, irc, link, org |
|
7 |
;; |
|
8 |
;; This file is part of GNU Emacs. |
|
9 |
;; |
|
10 |
;; GNU Emacs is free software: you can redistribute it and/or modify |
|
11 |
;; it under the terms of the GNU General Public License as published by |
|
12 |
;; the Free Software Foundation, either version 3 of the License, or |
|
13 |
;; (at your option) any later version. |
|
14 |
|
|
15 |
;; GNU Emacs is distributed in the hope that it will be useful, |
|
16 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18 |
;; GNU General Public License for more details. |
|
19 |
|
|
20 |
;; You should have received a copy of the GNU General Public License |
|
21 |
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
|
22 |
|
|
23 |
;;; Commentary: |
|
24 |
|
|
25 |
;; This file implements links to an IRC session from within Org mode. |
|
26 |
;; Org mode loads this module by default - if this is not what you want, |
|
27 |
;; configure the variable `org-modules'. |
|
28 |
;; |
|
29 |
;; Please customize the variable `org-modules' to select |
|
30 |
;; extensions you would like to use, and to deselect those which you don't |
|
31 |
;; want. |
|
32 |
;; |
|
33 |
;; Please note that at the moment only ERC is supported. Other clients |
|
34 |
;; shouldn't be difficult to add though. |
|
35 |
;; |
|
36 |
;; Then set `org-irc-link-to-logs' to non-nil if you would like a |
|
37 |
;; file:/ type link to be created to the current line in the logs or |
|
38 |
;; to t if you would like to create an irc:/ style link. |
|
39 |
;; |
|
40 |
;; Links within an org buffer might look like this: |
|
41 |
;; |
|
42 |
;; [[irc:/irc.freenode.net/#emacs/bob][chat with bob in #emacs on freenode]] |
|
43 |
;; [[irc:/irc.freenode.net/#emacs][#emacs on freenode]] |
|
44 |
;; [[irc:/irc.freenode.net/]] |
|
45 |
;; |
|
46 |
;; If, when the resulting link is visited, there is no connection to a |
|
47 |
;; requested server then one will be created. |
|
48 |
|
|
49 |
;;; Code: |
|
50 |
|
|
51 |
(require 'org) |
|
52 |
|
|
53 |
(declare-function erc-buffer-filter "erc" (predicate &optional proc)) |
|
54 |
(declare-function erc-channel-p "erc" (channel)) |
|
55 |
(declare-function erc-cmd-JOIN "erc" (channel &optional key)) |
|
56 |
(declare-function erc-current-logfile "erc-log" (&optional buffer)) |
|
57 |
(declare-function erc-default-target "erc" ()) |
|
58 |
(declare-function erc-get-server-nickname-list "erc" ()) |
|
59 |
(declare-function erc-logging-enabled "erc-log" (&optional buffer)) |
|
60 |
(declare-function erc-prompt "erc" ()) |
|
61 |
(declare-function erc-save-buffer-in-logs "erc-log" (&optional buffer)) |
|
62 |
(declare-function erc-server-buffer "erc" ()) |
|
63 |
|
|
64 |
(defvar org-irc-client 'erc |
|
65 |
"The IRC client to act on.") |
|
66 |
|
|
67 |
(defvar org-irc-link-to-logs nil |
|
68 |
"Non-nil will store a link to the logs, nil will store an irc: style link.") |
|
69 |
|
|
70 |
(defvar erc-default-port) ; dynamically scoped from erc.el |
|
71 |
(defvar erc-session-port) ; dynamically scoped form erc-backend.el |
|
72 |
(defvar erc-session-server) ; dynamically scoped form erc-backend.el |
|
73 |
|
|
74 |
;; Generic functions/config (extend these for other clients) |
|
75 |
|
|
76 |
(org-link-set-parameters "irc" :follow #'org-irc-visit :store #'org-irc-store-link) |
|
77 |
|
|
78 |
(defun org-irc-visit (link) |
|
79 |
"Parse LINK and dispatch to the correct function based on the client found." |
|
80 |
(let ((link (org-irc-parse-link link))) |
|
81 |
(cond |
|
82 |
((eq org-irc-client 'erc) |
|
83 |
(org-irc-visit-erc link)) |
|
84 |
(t |
|
85 |
(error "ERC only known client"))))) |
|
86 |
|
|
87 |
(defun org-irc-parse-link (link) |
|
88 |
"Parse an IRC LINK and return the attributes found. |
|
89 |
Parse a LINK that looks like server:port/chan/user (port, chan |
|
90 |
and user being optional) and return any of the port, channel or user |
|
91 |
attributes that are found." |
|
92 |
(let* ((parts (split-string link "/" t)) |
|
93 |
(len (length parts))) |
|
94 |
(when (or (< len 1) (> len 3)) |
|
95 |
(error "Failed to parse link needed 1-3 parts, got %d" len)) |
|
96 |
(setcar parts (split-string (car parts) ":" t)) |
|
97 |
parts)) |
|
98 |
|
|
99 |
;;;###autoload |
|
100 |
(defun org-irc-store-link () |
|
101 |
"Dispatch to the appropriate function to store a link to an IRC session." |
|
102 |
(cond |
|
103 |
((eq major-mode 'erc-mode) |
|
104 |
(org-irc-erc-store-link)))) |
|
105 |
|
|
106 |
(defun org-irc-ellipsify-description (string &optional after) |
|
107 |
"Remove unnecessary white space from STRING and add ellipses if necessary. |
|
108 |
Strip starting and ending white space from STRING and replace any |
|
109 |
chars that the value AFTER with `...'" |
|
110 |
(let* ((after (number-to-string (or after 30))) |
|
111 |
(replace-map (list (cons "^[ \t]*" "") |
|
112 |
(cons "[ \t]*$" "") |
|
113 |
(cons (concat "^\\(.\\{" after |
|
114 |
"\\}\\).*") "\\1...")))) |
|
115 |
(dolist (x replace-map string) |
|
116 |
(when (string-match (car x) string) |
|
117 |
(setq string (replace-match (cdr x) nil nil string)))))) |
|
118 |
|
|
119 |
;; ERC specific functions |
|
120 |
|
|
121 |
(defun org-irc-erc-get-line-from-log (erc-line) |
|
122 |
"Find the best line to link to from the ERC logs given ERC-LINE as a start. |
|
123 |
If the user is on the ERC-prompt then search backward for the |
|
124 |
first non-blank line, otherwise return the current line. The |
|
125 |
result is a cons of the filename and search string." |
|
126 |
(erc-save-buffer-in-logs) |
|
127 |
(require 'erc-log) |
|
128 |
(with-current-buffer (find-file-noselect (erc-current-logfile)) |
|
129 |
(goto-char (point-max)) |
|
130 |
(list |
|
131 |
(abbreviate-file-name buffer-file-name) |
|
132 |
;; can we get a '::' part? |
|
133 |
(if (string= erc-line (erc-prompt)) |
|
134 |
(progn |
|
135 |
(goto-char (point-at-bol)) |
|
136 |
(when (search-backward-regexp "^[^ ]" nil t) |
|
137 |
(buffer-substring-no-properties (point-at-bol) |
|
138 |
(point-at-eol)))) |
|
139 |
(when (search-backward erc-line nil t) |
|
140 |
(buffer-substring-no-properties (point-at-bol) |
|
141 |
(point-at-eol))))))) |
|
142 |
|
|
143 |
(defun org-irc-erc-store-link () |
|
144 |
"Store a link to the IRC log file or the session itself. |
|
145 |
Depending on the variable `org-irc-link-to-logs' store either a |
|
146 |
link to the log file for the current session or an irc: link to |
|
147 |
the session itself." |
|
148 |
(require 'erc-log) |
|
149 |
(if org-irc-link-to-logs |
|
150 |
(let* ((erc-line (buffer-substring-no-properties |
|
151 |
(point-at-bol) (point-at-eol))) |
|
152 |
(parsed-line (org-irc-erc-get-line-from-log erc-line))) |
|
153 |
(if (erc-logging-enabled nil) |
|
154 |
(progn |
|
155 |
(org-store-link-props |
|
156 |
:type "file" |
|
157 |
:description (concat "'" (org-irc-ellipsify-description |
|
158 |
(cadr parsed-line) 20) |
|
159 |
"' from an IRC conversation") |
|
160 |
:link (concat "file:" (car parsed-line) "::" |
|
161 |
(cadr parsed-line))) |
|
162 |
t) |
|
163 |
(error "This ERC session is not being logged"))) |
|
164 |
(let* ((link-text (org-irc-get-erc-link)) |
|
165 |
(link (org-irc-parse-link link-text))) |
|
166 |
(if link-text |
|
167 |
(progn |
|
168 |
(org-store-link-props |
|
169 |
:type "irc" |
|
170 |
:link (concat "irc:/" link-text) |
|
171 |
:description (concat "irc session `" link-text "'") |
|
172 |
:server (car (car link)) |
|
173 |
:port (or (string-to-number (cadr (pop link))) erc-default-port) |
|
174 |
:nick (pop link)) |
|
175 |
t) |
|
176 |
(error "Failed to create ('irc:/' style) ERC link"))))) |
|
177 |
|
|
178 |
(defun org-irc-get-erc-link () |
|
179 |
"Return an org compatible irc:/ link from an ERC buffer." |
|
180 |
(let* ((session-port (if (numberp erc-session-port) |
|
181 |
(number-to-string erc-session-port) |
|
182 |
erc-session-port)) |
|
183 |
(link (concat erc-session-server ":" session-port))) |
|
184 |
(concat link "/" |
|
185 |
(if (and (erc-default-target) |
|
186 |
(erc-channel-p (erc-default-target)) |
|
187 |
(car (get-text-property (point) 'erc-data))) |
|
188 |
;; we can get a nick |
|
189 |
(let ((nick (car (get-text-property (point) 'erc-data)))) |
|
190 |
(concat (erc-default-target) "/" nick)) |
|
191 |
(erc-default-target))))) |
|
192 |
|
|
193 |
(defun org-irc-get-current-erc-port () |
|
194 |
"Return the current port as a number. |
|
195 |
Return the current port number or, if none is set, return the ERC |
|
196 |
default." |
|
197 |
(cond |
|
198 |
((stringp erc-session-port) |
|
199 |
(string-to-number erc-session-port)) |
|
200 |
((numberp erc-session-port) |
|
201 |
erc-session-port) |
|
202 |
(t |
|
203 |
erc-default-port))) |
|
204 |
|
|
205 |
(defun org-irc-visit-erc (link) |
|
206 |
"Visit an ERC buffer based on criteria found in LINK." |
|
207 |
(require 'erc) |
|
208 |
(require 'erc-log) |
|
209 |
(let* ((server (car (car link))) |
|
210 |
(port (let ((p (cadr (pop link)))) |
|
211 |
(if p (string-to-number p) erc-default-port))) |
|
212 |
(server-buffer) |
|
213 |
(buffer-list |
|
214 |
(erc-buffer-filter |
|
215 |
(lambda nil |
|
216 |
(let ((tmp-server-buf (erc-server-buffer))) |
|
217 |
(and tmp-server-buf |
|
218 |
(with-current-buffer tmp-server-buf |
|
219 |
(and |
|
220 |
(eq (org-irc-get-current-erc-port) port) |
|
221 |
(string= erc-session-server server) |
|
222 |
(setq server-buffer tmp-server-buf))))))))) |
|
223 |
(if buffer-list |
|
224 |
(let ((chan-name (pop link))) |
|
225 |
;; if we got a channel name then switch to it or join it |
|
226 |
(if chan-name |
|
227 |
(let ((chan-buf (catch 'found |
|
228 |
(dolist (x buffer-list) |
|
229 |
(if (string= (buffer-name x) chan-name) |
|
230 |
(throw 'found x)))))) |
|
231 |
(if chan-buf |
|
232 |
(progn |
|
233 |
(pop-to-buffer-same-window chan-buf) |
|
234 |
;; if we got a nick, and they're in the chan, |
|
235 |
;; then start a chat with them |
|
236 |
(let ((nick (pop link))) |
|
237 |
(when nick |
|
238 |
(if (member nick (erc-get-server-nickname-list)) |
|
239 |
(progn |
|
240 |
(goto-char (point-max)) |
|
241 |
(insert (concat nick ": "))) |
|
242 |
(error "%s not found in %s" nick chan-name))))) |
|
243 |
(progn |
|
244 |
(pop-to-buffer-same-window server-buffer) |
|
245 |
(erc-cmd-JOIN chan-name)))) |
|
246 |
(pop-to-buffer-same-window server-buffer))) |
|
247 |
;; no server match, make new connection |
|
248 |
(erc-select :server server :port port)))) |
|
249 |
|
|
250 |
(provide 'org-irc) |
|
251 |
|
|
252 |
;; Local variables: |
|
253 |
;; generated-autoload-file: "org-loaddefs.el" |
|
254 |
;; End: |
|
255 |
|
|
256 |
;;; org-irc.el ends here |