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