aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTorsten Hilbrich2020-10-05 06:50:25 +0200
committerTorsten Hilbrich2020-10-08 05:56:31 +0200
commitb6227446d9166130cf6d30b0fc11428fe001c90c (patch)
tree5e3826e8071647aabb3e29b4f6c97928ee959e02
parent0a5e9cf2622a0282d56cc150af5a94b5d5fd71be (diff)
downloademacs-b6227446d9166130cf6d30b0fc11428fe001c90c.tar.gz
emacs-b6227446d9166130cf6d30b0fc11428fe001c90c.zip
Importing dictionary module
* lisp/net: Adding files connection.el, link.el, dictionary.el, imported from https://github.com/myrkr/dictionary-el.git
-rw-r--r--lisp/net/connection.el159
-rw-r--r--lisp/net/dictionary.el1367
-rw-r--r--lisp/net/link.el129
3 files changed, 1655 insertions, 0 deletions
diff --git a/lisp/net/connection.el b/lisp/net/connection.el
new file mode 100644
index 00000000000..3afcc2cb894
--- /dev/null
+++ b/lisp/net/connection.el
@@ -0,0 +1,159 @@
1;;; connection.el --- TCP-based client connection
2
3;; Author: Torsten Hilbrich <torsten.hilbrich@gmx.net>
4;; Keywords: network
5;; Version: 1.11
6
7;; This file is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation; either version 2, or (at your option)
10;; any later version.
11
12;; This file is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs; see the file COPYING. If not, write to
19;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20;; Boston, MA 02111-1307, USA.
21
22;;; Commentary:
23
24;; connection allows to handle TCP-based connections in client mode
25;; where text-based information are exchanged. There is special
26;; support for handling CR LF (and the usual CR LF . CR LF
27;; terminater).
28
29;;; Code:
30
31(eval-when-compile
32 (require 'cl))
33
34(defmacro connection-p (connection)
35 "Returns non-nil if `connection' is a connection object"
36 (list 'get connection ''connection))
37
38(defmacro connection-read-point (connection)
39 "Return the read point of the connection object."
40 (list 'get connection ''connection-read-point))
41
42(defmacro connection-process (connection)
43 "Return the process of the connection object."
44 (list 'get connection ''connection-process))
45
46(defmacro connection-buffer (connection)
47 "Return the buffer of the connection object."
48 (list 'get connection ''connection-buffer))
49
50(defmacro connection-set-read-point (connection point)
51 "Set the read-point for `connection' to `point'."
52 (list 'put connection ''connection-read-point point))
53
54(defmacro connection-set-process (connection process)
55 "Set the process for `connection' to `process'."
56 (list 'put connection ''connection-process process))
57
58(defmacro connection-set-buffer (connection buffer)
59 "Set the buffer for `connection' to `buffer'."
60 (list 'put connection ''connection-buffer buffer))
61
62(defun connection-create-data (buffer process point)
63 "Create a new connection data based on `buffer', `process', and `point'."
64 (let ((connection (make-symbol "connection")))
65 (put connection 'connection t)
66 (connection-set-read-point connection point)
67 (connection-set-process connection process)
68 (connection-set-buffer connection buffer)
69 connection))
70
71(defun connection-open (server port)
72 "Open a connection to `server' and `port'.
73A data structure identifing the connection is returned"
74
75 (let ((process-buffer (generate-new-buffer (format " connection to %s:%s"
76 server
77 port)))
78 (process))
79 (with-current-buffer process-buffer
80 (setq process (open-network-stream "connection" process-buffer
81 server port))
82 (connection-create-data process-buffer process (point-min)))))
83
84(defun connection-status (connection)
85 "Return the status of the connection.
86Possible return values are the symbols:
87nil: argument is no connection object
88'none: argument has no connection
89'up: connection is open and buffer is existing
90'down: connection is closed
91'alone: connection is not associated with a buffer"
92 (if (connection-p connection)
93 (let ((process (connection-process connection))
94 (buffer (connection-buffer connection)))
95 (if (not process)
96 'none
97 (if (not (buffer-live-p buffer))
98 'alone
99 (if (not (eq (process-status process) 'open))
100 'down
101 'up))))
102 nil))
103
104(defun connection-close (connection)
105 "Force closing of the connection."
106 (if (connection-p connection)
107 (progn
108 (let ((buffer (connection-buffer connection))
109 (process (connection-process connection)))
110 (if process
111 (delete-process process))
112 (if buffer
113 (kill-buffer buffer))
114
115 (connection-set-process connection nil)
116 (connection-set-buffer connection nil)))))
117
118(defun connection-send (connection data)
119 "Send `data' to the process."
120 (unless (eq (connection-status connection) 'up)
121 (error "Connection is not up"))
122 (with-current-buffer (connection-buffer connection)
123 (goto-char (point-max))
124 (connection-set-read-point connection (point))
125 (process-send-string (connection-process connection) data)))
126
127(defun connection-send-crlf (connection data)
128 "Send `data' together with CRLF to the process."
129 (connection-send connection (concat data "\r\n")))
130
131(defun connection-read (connection delimiter)
132 "Read data until `delimiter' is found inside the buffer."
133 (unless (eq (connection-status connection) 'up)
134 (error "Connection is not up"))
135 (let ((case-fold-search nil)
136 match-end)
137 (with-current-buffer (connection-buffer connection)
138 (goto-char (connection-read-point connection))
139 ;; Wait until there is enough data
140 (while (not (search-forward-regexp delimiter nil t))
141 (accept-process-output (connection-process connection) 3)
142 (goto-char (connection-read-point connection)))
143 (setq match-end (point))
144 ;; Return the result
145 (let ((result (buffer-substring (connection-read-point connection)
146 match-end)))
147 (connection-set-read-point connection match-end)
148 result))))
149
150(defun connection-read-crlf (connection)
151 "Read until a line is completedx with CRLF"
152 (connection-read connection "\015?\012"))
153
154(defun connection-read-to-point (connection)
155 "Read until a line is consisting of a single point"
156 (connection-read connection "\015?\012[.]\015?\012"))
157
158(provide 'connection)
159;;; connection.el ends here
diff --git a/lisp/net/dictionary.el b/lisp/net/dictionary.el
new file mode 100644
index 00000000000..9545926cb25
--- /dev/null
+++ b/lisp/net/dictionary.el
@@ -0,0 +1,1367 @@
1;;; dictionary.el --- Client for rfc2229 dictionary servers
2
3;; Author: Torsten Hilbrich <torsten.hilbrich@gmx.net>
4;; Keywords: interface, dictionary
5;; Version: 1.11
6;; Package-Requires: ((connection "1.11") (link "1.11"))
7
8;; This file is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation; either version 2, or (at your option)
11;; any later version.
12
13;; This file is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with GNU Emacs; see the file COPYING. If not, write to
20;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21;; Boston, MA 02111-1307, USA.
22
23;;; Commentary:
24
25;; dictionary allows you to interact with dictionary servers.
26;; Use M-x customize-group dictionary to modify user settings.
27;;
28;; Main functions for interaction are:
29;; dictionary - opens a new dictionary buffer
30;; dictionary-search - search for the definition of a word
31;;
32;; You can find more information in the README file of the GitHub
33;; repository https://github.com/myrkr/dictionary-el
34
35;;; Code:
36
37(eval-when-compile
38 (require 'cl))
39
40(require 'easymenu)
41(require 'custom)
42(require 'connection)
43(require 'link)
44
45;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
46;; Stuff for customizing.
47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
48
49(eval-when-compile
50 (unless (fboundp 'defface)
51 (message "Please update your custom.el file: %s"
52 "http://www.dina.kvl.dk/~abraham/custom/"))
53
54 (unless (fboundp 'defgroup)
55 (defmacro defgroup (&rest ignored))
56 (defmacro defcustom (var value doc &rest ignored)
57 (list 'defvar var value doc))))
58
59(defvar dictionary-server)
60(defun dictionary-set-server-var (name value)
61 (if (and (boundp 'dictionary-connection)
62 dictionary-connection
63 (eq (connection-status dictionary-connection) 'up)
64 (y-or-n-p
65 (concat "Close existing connection to " dictionary-server "? ")))
66 (connection-close dictionary-connection))
67 (set-default name value))
68
69(defgroup dictionary nil
70 "Client for accessing the dictd server based dictionaries"
71 :group 'hypermedia)
72
73(defgroup dictionary-proxy nil
74 "Proxy configuration options for the dictionary client"
75 :group 'dictionary)
76
77(defcustom dictionary-server
78 "dict.org"
79 "This server is contacted for searching the dictionary"
80 :group 'dictionary
81 :set 'dictionary-set-server-var
82 :type 'string)
83
84(defcustom dictionary-port
85 2628
86 "The port of the dictionary server.
87 This port is propably always 2628 so there should be no need to modify it."
88 :group 'dictionary
89 :set 'dictionary-set-server-var
90 :type 'number)
91
92(defcustom dictionary-identification
93 "dictionary.el emacs lisp dictionary client"
94 "This is the identification string that will be sent to the server."
95 :group 'dictionary
96 :type 'string)
97
98(defcustom dictionary-default-dictionary
99 "*"
100 "The dictionary which is used for searching definitions and matching.
101 * and ! have a special meaning, * search all dictionaries, ! search until
102 one dictionary yields matches."
103 :group 'dictionary
104 :type 'string)
105
106(defcustom dictionary-default-strategy
107 "."
108 "The default strategy for listing matching words."
109 :group 'dictionary
110 :type 'string)
111
112(defcustom dictionary-default-popup-strategy
113 "exact"
114 "The default strategy for listing matching words within a popup window.
115
116The following algorithm (defined by the dictd server) are supported
117by the choice value:
118
119- Exact match
120
121 The found word exactly matches the searched word.
122
123- Similiar sounding
124
125 The found word sounds similiar to the searched word. For this match type
126 the soundex algorithm defined by Donald E. Knuth is used. It will only
127 works with english words and the algorithm is not very reliable (i.e.,
128 the soundex algorithm is quite simple).
129
130- Levenshtein distance one
131
132 The Levenshtein distance is defined as the number of insertions, deletions,
133 or replacements needed to get the searched word. This algorithm searches
134 for word where spelling mistakes are allowed. Levenshtein distance one
135 means there is either a deleted character, an inserted character, or a
136 modified one.
137
138- User choice
139
140 Here you can enter any matching algorithm supported by your
141 dictionary server.
142"
143 :group 'dictionary
144 :type '(choice (const :tag "Exact match" "exact")
145 (const :tag "Similiar sounding" "soundex")
146 (const :tag "Levenshtein distance one" "lev")
147 (string :tag "User choice")))
148
149(defcustom dictionary-create-buttons
150 t
151 "Create some clickable buttons on top of the window if non-nil."
152 :group 'dictionary
153 :type 'boolean)
154
155(defcustom dictionary-mode-hook
156 nil
157 "Hook run in dictionary mode buffers."
158 :group 'dictionary
159 :type 'hook)
160
161(defcustom dictionary-use-http-proxy
162 nil
163 "Connects via a HTTP proxy using the CONNECT command when not nil."
164 :group 'dictionary-proxy
165 :set 'dictionary-set-server-var
166 :type 'boolean)
167
168(defcustom dictionary-proxy-server
169 "proxy"
170 "The name of the HTTP proxy to use when dictionary-use-http-proxy is set."
171 :group 'dictionary-proxy
172 :set 'dictionary-set-server-var
173 :type 'string)
174
175(defcustom dictionary-proxy-port
176 3128
177 "The port of the proxy server, used only when dictionary-use-http-proxy is set."
178 :group 'dictionary-proxy
179 :set 'dictionary-set-server-var
180 :type 'number)
181
182(defcustom dictionary-use-single-buffer
183 nil
184 "Should the dictionary command reuse previous dictionary buffers?"
185 :group 'dictionary
186 :type 'boolean)
187
188(defcustom dictionary-description-open-delimiter
189 ""
190 "The delimiter to display in front of the dictionaries description"
191 :group 'dictionary
192 :type 'string)
193
194(defcustom dictionary-description-close-delimiter
195 ""
196 "The delimiter to display after of the dictionaries description"
197 :group 'dictionary
198 :type 'string)
199
200;; Define only when coding-system-list is available
201(when (fboundp 'coding-system-list)
202 (defcustom dictionary-coding-systems-for-dictionaries
203 '( ("mueller" . koi8-r))
204 "Mapping of dictionaries to coding systems.
205 Each entry in this list defines the coding system to be used for that
206 dictionary. The default coding system for all other dictionaries
207 is utf-8"
208 :group 'dictionary
209 :type `(repeat (cons :tag "Association"
210 (string :tag "Dictionary name")
211 (choice :tag "Coding system"
212 :value 'utf-8
213 ,@(mapcar (lambda (x) (list 'const x))
214 (coding-system-list))
215 ))))
216
217 )
218
219(if (fboundp 'defface)
220 (progn
221
222 (defface dictionary-word-definition-face
223 '((((supports (:family "DejaVu Serif")))
224 (:family "DejaVu Serif"))
225 (((type x))
226 (:font "Sans Serif"))
227 (t
228 (:font "default")))
229 "The face that is used for displaying the definition of the word."
230 :group 'dictionary)
231
232 (defface dictionary-word-entry-face
233 '((((type x))
234 (:italic t))
235 (((type tty) (class color))
236 (:foreground "green"))
237 (t
238 (:inverse t)))
239 "The face that is used for displaying the initial word entry line."
240 :group 'dictionary)
241
242 (defface dictionary-button-face
243 '((t
244 (:bold t)))
245 "The face that is used for displaying buttons."
246 :group 'dictionary)
247
248 (defface dictionary-reference-face
249 '((((type x)
250 (class color)
251 (background dark))
252 (:foreground "yellow"))
253 (((type tty)
254 (class color)
255 (background dark))
256 (:foreground "cyan"))
257 (((class color)
258 (background light))
259 (:foreground "blue"))
260 (t
261 (:underline t)))
262
263 "The face that is used for displaying a reference word."
264 :group 'dictionary)
265
266 )
267
268 ;; else
269 (copy-face 'italic 'dictionary-word-entry-face)
270 (copy-face 'bold 'dictionary-button-face)
271 (copy-face 'default 'dictionary-reference-face)
272 (set-face-foreground 'dictionary-reference-face "blue"))
273
274;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
275;; Buffer local variables for storing the current state
276;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277
278(defvar dictionary-window-configuration
279 nil
280 "The window configuration to be restored upon closing the buffer")
281
282(defvar dictionary-selected-window
283 nil
284 "The currently selected window")
285
286(defvar dictionary-position-stack
287 nil
288 "The history buffer for point and window position")
289
290(defvar dictionary-data-stack
291 nil
292 "The history buffer for functions and arguments")
293
294(defvar dictionary-positions
295 nil
296 "The current positions")
297
298(defvar dictionary-current-data
299 nil
300 "The item that will be placed on stack next time")
301
302;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
303;; Global variables
304;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
305(defvar dictionary-mode-map
306 nil
307 "Keymap for dictionary mode")
308
309(defvar dictionary-connection
310 nil
311 "The current network connection")
312
313(defvar dictionary-instances
314 0
315 "The number of open dictionary buffers")
316
317(defvar dictionary-marker
318 nil
319 "Stores the point position while buffer display.")
320
321(defvar dictionary-color-support
322 (condition-case nil
323 (x-display-color-p)
324 (error nil))
325 "Determines if the Emacs has support to display color")
326
327;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
328;; Basic function providing startup actions
329;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
330
331;;;###autoload
332(defun dictionary-mode ()
333 "This is a mode for searching a dictionary server implementing
334 the protocol defined in RFC 2229.
335
336 This is a quick reference to this mode describing the default key bindings:
337
338 * q close the dictionary buffer
339 * h display this help information
340 * s ask for a new word to search
341 * d search the word at point
342 * n or Tab place point to the next link
343 * p or S-Tab place point to the prev link
344
345 * m ask for a pattern and list all matching words.
346 * D select the default dictionary
347 * M select the default search strategy
348
349 * Return or Button2 visit that link
350 * M-Return or M-Button2 search the word beneath link in all dictionaries
351 "
352
353 (unless (eq major-mode 'dictionary-mode)
354 (incf dictionary-instances))
355
356 (kill-all-local-variables)
357 (buffer-disable-undo)
358 (use-local-map dictionary-mode-map)
359 (setq major-mode 'dictionary-mode)
360 (setq mode-name "Dictionary")
361
362 (make-local-variable 'dictionary-data-stack)
363 (setq dictionary-data-stack nil)
364 (make-local-variable 'dictionary-position-stack)
365 (setq dictionary-position-stack nil)
366
367 (make-local-variable 'dictionary-current-data)
368 (make-local-variable 'dictionary-positions)
369
370 (make-local-variable 'dictionary-default-dictionary)
371 (make-local-variable 'dictionary-default-strategy)
372
373 (if (featurep 'xemacs)
374 (make-local-hook 'kill-buffer-hook))
375 (add-hook 'kill-buffer-hook 'dictionary-close t t)
376 (run-hooks 'dictionary-mode-hook))
377
378;;;###autoload
379(defun dictionary ()
380 "Create a new dictonary buffer and install dictionary-mode"
381 (interactive)
382 (let ((buffer (or (and dictionary-use-single-buffer
383 (get-buffer "*Dictionary*"))
384 (generate-new-buffer "*Dictionary*")))
385 (window-configuration (current-window-configuration))
386 (selected-window (frame-selected-window)))
387
388 (switch-to-buffer-other-window buffer)
389 (dictionary-mode)
390
391 (make-local-variable 'dictionary-window-configuration)
392 (make-local-variable 'dictionary-selected-window)
393 (setq dictionary-window-configuration window-configuration)
394 (setq dictionary-selected-window selected-window)
395 (dictionary-check-connection)
396 (dictionary-new-buffer)
397 (dictionary-store-positions)
398 (dictionary-store-state 'dictionary-new-buffer nil)))
399
400(defun dictionary-new-buffer (&rest ignore)
401 "Create a new and clean buffer"
402
403 (dictionary-pre-buffer)
404 (dictionary-post-buffer))
405
406
407(unless dictionary-mode-map
408 (setq dictionary-mode-map (make-sparse-keymap))
409 (suppress-keymap dictionary-mode-map)
410
411 (define-key dictionary-mode-map "q" 'dictionary-close)
412 (define-key dictionary-mode-map "h" 'dictionary-help)
413 (define-key dictionary-mode-map "s" 'dictionary-search)
414 (define-key dictionary-mode-map "d" 'dictionary-lookup-definition)
415 (define-key dictionary-mode-map "D" 'dictionary-select-dictionary)
416 (define-key dictionary-mode-map "M" 'dictionary-select-strategy)
417 (define-key dictionary-mode-map "m" 'dictionary-match-words)
418 (define-key dictionary-mode-map "l" 'dictionary-previous)
419
420 (if (and (string-match "GNU" (emacs-version))
421 (not window-system))
422 (define-key dictionary-mode-map [9] 'dictionary-next-link)
423 (define-key dictionary-mode-map [tab] 'dictionary-next-link))
424
425 ;; shift-tabs normally is supported on window systems only, but
426 ;; I do not enforce it
427 (define-key dictionary-mode-map [(shift tab)] 'dictionary-prev-link)
428 (define-key dictionary-mode-map "\e\t" 'dictionary-prev-link)
429 (define-key dictionary-mode-map [backtab] 'dictionary-prev-link)
430
431 (define-key dictionary-mode-map "n" 'dictionary-next-link)
432 (define-key dictionary-mode-map "p" 'dictionary-prev-link)
433
434 (define-key dictionary-mode-map " " 'scroll-up)
435 (define-key dictionary-mode-map [(meta space)] 'scroll-down)
436
437 (link-initialize-keymap dictionary-mode-map))
438
439(defmacro dictionary-reply-code (reply)
440 "Return the reply code stored in `reply'."
441 (list 'get reply ''reply-code))
442
443(defmacro dictionary-reply (reply)
444 "Return the string reply stored in `reply'."
445 (list 'get reply ''reply))
446
447(defmacro dictionary-reply-list (reply)
448 "Return the reply list stored in `reply'."
449 (list 'get reply ''reply-list))
450
451(defun dictionary-check-connection ()
452 "Check if there is already a connection open"
453 (if (not (and dictionary-connection
454 (eq (connection-status dictionary-connection) 'up)))
455 (let ((wanted 'raw-text)
456 (coding-system nil))
457 (if (and (fboundp 'coding-system-list)
458 (member wanted (coding-system-list)))
459 (setq coding-system wanted))
460 (let ((coding-system-for-read coding-system)
461 (coding-system-for-write coding-system))
462 (message "Opening connection to %s:%s" dictionary-server
463 dictionary-port)
464 (connection-close dictionary-connection)
465 (setq dictionary-connection
466 (if dictionary-use-http-proxy
467 (connection-open dictionary-proxy-server
468 dictionary-proxy-port)
469 (connection-open dictionary-server dictionary-port)))
470 (set-process-query-on-exit-flag
471 (connection-process dictionary-connection)
472 nil)
473
474 (when dictionary-use-http-proxy
475 (message "Proxy CONNECT to %s:%d"
476 dictionary-proxy-server
477 dictionary-proxy-port)
478 (dictionary-send-command (format "CONNECT %s:%d HTTP/1.1"
479 dictionary-server
480 dictionary-port))
481 ;; just a \r\n combination
482 (dictionary-send-command "")
483
484 ;; read first line of reply
485 (let* ((reply (dictionary-read-reply))
486 (reply-list (dictionary-split-string reply)))
487 ;; first item is protocol, second item is code
488 (unless (= (string-to-number (cadr reply-list)) 200)
489 (error "Bad reply from proxy server %s" reply))
490
491 ;; skip the following header lines until empty found
492 (while (not (equal reply ""))
493 (setq reply (dictionary-read-reply)))))
494
495 (dictionary-check-initial-reply)
496 (dictionary-send-command (concat "client " dictionary-identification))
497 (let ((reply (dictionary-read-reply-and-split)))
498 (message nil)
499 (unless (dictionary-check-reply reply 250)
500 (error "Unknown server answer: %s"
501 (dictionary-reply reply))))))))
502
503(defun dictionary-mode-p ()
504 "Return non-nil if current buffer has dictionary-mode"
505 (eq major-mode 'dictionary-mode))
506
507(defun dictionary-ensure-buffer ()
508 "If current buffer is not a dictionary buffer, create a new one."
509 (unless (dictionary-mode-p)
510 (dictionary)))
511
512;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
513;; Dealing with closing the buffer
514;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
515
516(defun dictionary-close (&rest ignore)
517 "Close the current dictionary buffer and its connection"
518 (interactive)
519 (if (eq major-mode 'dictionary-mode)
520 (progn
521 (setq major-mode nil)
522 (if (<= (decf dictionary-instances) 0)
523 (connection-close dictionary-connection))
524 (let ((configuration dictionary-window-configuration)
525 (selected-window dictionary-selected-window))
526 (kill-buffer (current-buffer))
527 (set-window-configuration configuration)
528 (select-window selected-window)))))
529
530;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
531;; Helpful functions
532;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
533
534(defun dictionary-send-command (string)
535 "Send the command `string' to the network connection."
536 (dictionary-check-connection)
537 ;;;; #####
538 (connection-send-crlf dictionary-connection string))
539
540(defun dictionary-read-reply ()
541 "Read the reply line from the server"
542 (let ((answer (connection-read-crlf dictionary-connection)))
543 (if (string-match "\r?\n" answer)
544 (substring answer 0 (match-beginning 0))
545 answer)))
546
547(defun dictionary-split-string (string)
548 "Split the `string' constiting of space separated words into elements.
549This function knows about the special meaning of quotes (\")"
550 (let ((list))
551 (while (and string (> (length string) 0))
552 (let ((search "\\(\\s-+\\)")
553 (start 0))
554 (if (= (aref string 0) ?\")
555 (setq search "\\(\"\\)\\s-*"
556 start 1))
557 (if (string-match search string start)
558 (progn
559 (setq list (cons (substring string start (- (match-end 1) 1)) list)
560 string (substring string (match-end 0))))
561 (setq list (cons string list)
562 string nil))))
563 (nreverse list)))
564
565(defun dictionary-read-reply-and-split ()
566 "Read the reply, split it into words and return it"
567 (let ((answer (make-symbol "reply-data"))
568 (reply (dictionary-read-reply)))
569 (let ((reply-list (dictionary-split-string reply)))
570 (put answer 'reply reply)
571 (put answer 'reply-list reply-list)
572 (put answer 'reply-code (string-to-number (car reply-list)))
573 answer)))
574
575(defun dictionary-read-answer ()
576 "Read an answer delimited by a . on a single line"
577 (let ((answer (connection-read-to-point dictionary-connection))
578 (start 0))
579 (while (string-match "\r\n" answer start)
580 (setq answer (replace-match "\n" t t answer))
581 (setq start (1- (match-end 0))))
582 (setq start 0)
583 (if (string-match "\n\\.\n.*" answer start)
584 (setq answer (replace-match "" t t answer)))
585 answer))
586
587(defun dictionary-check-reply (reply code)
588 "Check if the reply in `reply' has the `code'."
589 (let ((number (dictionary-reply-code reply)))
590 (and (numberp number)
591 (= number code))))
592
593(defun dictionary-coding-system (dictionary)
594 "Select coding system to use for that dictionary"
595 (when (boundp 'dictionary-coding-systems-for-dictionaries)
596 (let ((coding-system
597 (or (cdr (assoc dictionary
598 dictionary-coding-systems-for-dictionaries))
599 'utf-8)))
600 (if (member coding-system (coding-system-list))
601 coding-system
602 nil))))
603
604(defun dictionary-decode-charset (text dictionary)
605 "Convert the text from the charset defined by the dictionary given."
606 (let ((coding-system (dictionary-coding-system dictionary)))
607 (if coding-system
608 (decode-coding-string text coding-system)
609 text)))
610
611(defun dictionary-encode-charset (text dictionary)
612 "Convert the text to the charset defined by the dictionary given."
613 (let ((coding-system (dictionary-coding-system dictionary)))
614 (if coding-system
615 (encode-coding-string text coding-system)
616 text)))
617
618;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
619;; Communication functions
620;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
621
622(defun dictionary-check-initial-reply ()
623 "Read the first reply from server and check it."
624 (let ((reply (dictionary-read-reply-and-split)))
625 (unless (dictionary-check-reply reply 220)
626 (connection-close dictionary-connection)
627 (error "Server returned: %s" (dictionary-reply reply)))))
628
629;; Store the current state
630(defun dictionary-store-state (function data)
631 "Stores the current state of operation for later restore."
632
633 (if dictionary-current-data
634 (progn
635 (push dictionary-current-data dictionary-data-stack)
636 (unless dictionary-positions
637 (error "dictionary-store-state called before dictionary-store-positions"))
638 (push dictionary-positions dictionary-position-stack)))
639 (setq dictionary-current-data
640 (cons function data)))
641
642(defun dictionary-store-positions ()
643 "Stores the current positions for later restore."
644
645 (setq dictionary-positions (cons (point) (window-start))))
646
647;; Restore the previous state
648(defun dictionary-restore-state (&rest ignored)
649 "Restore the state just before the last operation"
650 (let ((position (pop dictionary-position-stack))
651 (data (pop dictionary-data-stack)))
652 (unless position
653 (error "Already at begin of history"))
654 (apply (car data) (cdr data))
655 (set-window-start (selected-window) (cdr position))
656 (goto-char (car position))
657 (setq dictionary-current-data data)))
658
659;; The normal search
660
661(defun dictionary-new-search (args &optional all)
662 "Save the current state and start a new search"
663 (interactive)
664 (dictionary-store-positions)
665 (let ((word (car args))
666 (dictionary (cdr args)))
667
668 (if all
669 (setq dictionary dictionary-default-dictionary))
670 (dictionary-ensure-buffer)
671 (dictionary-new-search-internal word dictionary 'dictionary-display-search-result)
672 (dictionary-store-state 'dictionary-new-search-internal
673 (list word dictionary 'dictionary-display-search-result))))
674
675(defun dictionary-new-search-internal (word dictionary function)
676 "Starts a new search after preparing the buffer"
677 (dictionary-pre-buffer)
678 (dictionary-do-search word dictionary function))
679
680(defun dictionary-do-search (word dictionary function &optional nomatching)
681 "The workhorse for doing the search"
682
683 (message "Searching for %s in %s" word dictionary)
684 (dictionary-send-command (concat "define "
685 (dictionary-encode-charset dictionary "")
686 " \""
687 (dictionary-encode-charset word dictionary)
688 "\""))
689
690 (message nil)
691 (let ((reply (dictionary-read-reply-and-split)))
692 (if (dictionary-check-reply reply 552)
693 (progn
694 (unless nomatching
695 (beep)
696 (insert "Word not found, maybe you are looking "
697 "for one of these words\n\n")
698 (dictionary-do-matching word
699 dictionary
700 "."
701 'dictionary-display-only-match-result)
702 (dictionary-post-buffer)))
703 (if (dictionary-check-reply reply 550)
704 (error "Dictionary \"%s\" is unknown, please select an existing one."
705 dictionary)
706 (unless (dictionary-check-reply reply 150)
707 (error "Unknown server answer: %s" (dictionary-reply reply)))
708 (funcall function reply)))))
709
710(defun dictionary-pre-buffer ()
711 "These commands are executed at the begin of a new buffer"
712 (setq buffer-read-only nil)
713 (erase-buffer)
714 (if dictionary-create-buttons
715 (progn
716 (link-insert-link "[Back]" 'dictionary-button-face
717 'dictionary-restore-state nil
718 "Mouse-2 to go backwards in history")
719 (insert " ")
720 (link-insert-link "[Search Definition]"
721 'dictionary-button-face
722 'dictionary-search nil
723 "Mouse-2 to look up a new word")
724 (insert " ")
725
726 (link-insert-link "[Matching words]"
727 'dictionary-button-face
728 'dictionary-match-words nil
729 "Mouse-2 to find matches for a pattern")
730 (insert " ")
731
732 (link-insert-link "[Quit]" 'dictionary-button-face
733 'dictionary-close nil
734 "Mouse-2 to close this window")
735
736 (insert "\n ")
737
738 (link-insert-link "[Select Dictionary]"
739 'dictionary-button-face
740 'dictionary-select-dictionary nil
741 "Mouse-2 to select dictionary for future searches")
742 (insert " ")
743 (link-insert-link "[Select Match Strategy]"
744 'dictionary-button-face
745 'dictionary-select-strategy nil
746 "Mouse-2 to select matching algorithm")
747 (insert "\n\n")))
748 (setq dictionary-marker (point-marker)))
749
750(defun dictionary-post-buffer ()
751 "These commands are executed at the end of a new buffer"
752 (goto-char dictionary-marker)
753
754 (set-buffer-modified-p nil)
755 (setq buffer-read-only t))
756
757(defun dictionary-display-search-result (reply)
758 "This function starts displaying the result starting with the `reply'."
759
760 (let ((number (nth 1 (dictionary-reply-list reply))))
761 (insert number (if (equal number "1")
762 " definition"
763 " definitions")
764 " found\n\n")
765 (setq reply (dictionary-read-reply-and-split))
766 (while (dictionary-check-reply reply 151)
767 (let* ((reply-list (dictionary-reply-list reply))
768 (dictionary (nth 2 reply-list))
769 (description (nth 3 reply-list))
770 (word (nth 1 reply-list)))
771 (dictionary-display-word-entry word dictionary description)
772 (setq reply (dictionary-read-answer))
773 (dictionary-display-word-definition reply word dictionary)
774 (setq reply (dictionary-read-reply-and-split))))
775 (dictionary-post-buffer)))
776
777(defun dictionary-display-word-entry (word dictionary description)
778 "Insert an explanation for the current definition."
779 (let ((start (point)))
780 (insert "From "
781 dictionary-description-open-delimiter
782 (dictionary-decode-charset description dictionary)
783 dictionary-description-close-delimiter
784 " [" (dictionary-decode-charset dictionary dictionary) "]:"
785 "\n\n")
786 (put-text-property start (point) 'face 'dictionary-word-entry-face)))
787
788(defun dictionary-display-word-definition (reply word dictionary)
789 "Insert the definition for the current word"
790 (let ((start (point)))
791 (insert (dictionary-decode-charset reply dictionary))
792 (insert "\n\n")
793 (put-text-property start (point) 'face 'dictionary-word-definition-face)
794 (let ((regexp "\\({+\\)\\([^ '\"][^}]*\\)\\(}+\\)"))
795 (goto-char start)
796 (while (< (point) (point-max))
797 (if (search-forward-regexp regexp nil t)
798 (let ((match-start (match-beginning 2))
799 (match-end (match-end 2)))
800 (if dictionary-color-support
801 ;; Compensate for the replacement
802 (let ((brace-match-length (- (match-end 1)
803 (match-beginning 1))))
804 (setq match-start (- (match-beginning 2)
805 brace-match-length))
806 (setq match-end (- (match-end 2)
807 brace-match-length))
808 (replace-match "\\2")))
809 (dictionary-mark-reference match-start match-end
810 'dictionary-new-search
811 word dictionary))
812 (goto-char (point-max)))))))
813
814(defun dictionary-mark-reference (start end call displayed-word dictionary)
815 "Format the area from `start' to `end' as link calling `call'.
816The word is taken from the buffer, the `dictionary' is given as argument."
817 (let ((word (buffer-substring-no-properties start end)))
818 (while (string-match "\n\\s-*" word)
819 (setq word (replace-match " " t t word)))
820 (while (string-match "[*\"]" word)
821 (setq word (replace-match "" t t word)))
822
823 (unless (equal word displayed-word)
824 (link-create-link start end 'dictionary-reference-face
825 call (cons word dictionary)
826 (concat "Press Mouse-2 to lookup \""
827 word "\" in \"" dictionary "\"")))))
828
829(defun dictionary-select-dictionary (&rest ignored)
830 "Save the current state and start a dictionary selection"
831 (interactive)
832 (dictionary-ensure-buffer)
833 (dictionary-store-positions)
834 (dictionary-do-select-dictionary)
835 (dictionary-store-state 'dictionary-do-select-dictionary nil))
836
837(defun dictionary-do-select-dictionary (&rest ignored)
838 "The workhorse for doing the dictionary selection."
839
840 (message "Looking up databases and descriptions")
841 (dictionary-send-command "show db")
842
843 (let ((reply (dictionary-read-reply-and-split)))
844 (message nil)
845 (if (dictionary-check-reply reply 554)
846 (error "No dictionary present")
847 (unless (dictionary-check-reply reply 110)
848 (error "Unknown server answer: %s"
849 (dictionary-reply reply)))
850 (dictionary-display-dictionarys reply))))
851
852(defun dictionary-simple-split-string (string &optional pattern)
853 "Return a list of substrings of STRING which are separated by PATTERN.
854If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"."
855 (or pattern
856 (setq pattern "[ \f\t\n\r\v]+"))
857 ;; The FSF version of this function takes care not to cons in case
858 ;; of infloop. Maybe we should synch?
859 (let (parts (start 0))
860 (while (string-match pattern string start)
861 (setq parts (cons (substring string start (match-beginning 0)) parts)
862 start (match-end 0)))
863 (nreverse (cons (substring string start) parts))))
864
865(defun dictionary-display-dictionarys (reply)
866 "Handle the display of all dictionaries existing on the server"
867 (dictionary-pre-buffer)
868 (insert "Please select your default dictionary:\n\n")
869 (dictionary-display-dictionary-line "* \"All dictionaries\"")
870 (dictionary-display-dictionary-line "! \"The first matching dictionary\"")
871 (let* ((reply (dictionary-read-answer))
872 (list (dictionary-simple-split-string reply "\n+")))
873 (mapc 'dictionary-display-dictionary-line list))
874 (dictionary-post-buffer))
875
876(defun dictionary-display-dictionary-line (string)
877 "Display a single dictionary"
878 (let* ((list (dictionary-split-string string))
879 (dictionary (car list))
880 (description (cadr list))
881 (translated (dictionary-decode-charset description dictionary)))
882 (if dictionary
883 (if (equal dictionary "--exit--")
884 (insert "(end of default search list)\n")
885 (link-insert-link (concat dictionary ": " translated)
886 'dictionary-reference-face
887 'dictionary-set-dictionary
888 (cons dictionary description)
889 "Mouse-2 to select this dictionary")
890 (insert "\n")))))
891
892(defun dictionary-set-dictionary (param &optional more)
893 "Select this dictionary as new default"
894
895 (if more
896 (dictionary-display-more-info param)
897 (let ((dictionary (car param)))
898 (setq dictionary-default-dictionary dictionary)
899 (dictionary-restore-state)
900 (message "Dictionary %s has been selected" dictionary))))
901
902(defun dictionary-display-more-info (param)
903 "Display the available information on the dictionary"
904
905 (let ((dictionary (car param))
906 (description (cdr param)))
907 (unless (or (equal dictionary "*")
908 (equal dictionary "!"))
909 (dictionary-store-positions)
910 (message "Requesting more information on %s" dictionary)
911 (dictionary-send-command
912 (concat "show info " (dictionary-encode-charset dictionary "")))
913 (let ((reply (dictionary-read-reply-and-split)))
914 (message nil)
915 (if (dictionary-check-reply reply 550)
916 (error "Dictionary \"%s\" not existing" dictionary)
917 (unless (dictionary-check-reply reply 112)
918 (error "Unknown server answer: %s" (dictionary-reply reply)))
919 (dictionary-pre-buffer)
920 (insert "Information on dictionary: ")
921 (link-insert-link description 'dictionary-reference-face
922 'dictionary-set-dictionary
923 (cons dictionary description)
924 "Mouse-2 to select this dictionary")
925 (insert "\n\n")
926 (setq reply (dictionary-read-answer))
927 (insert reply)
928 (dictionary-post-buffer)))
929
930 (dictionary-store-state 'dictionary-display-more-info dictionary))))
931
932(defun dictionary-select-strategy (&rest ignored)
933 "Save the current state and start a strategy selection"
934 (interactive)
935 (dictionary-ensure-buffer)
936 (dictionary-store-positions)
937 (dictionary-do-select-strategy)
938 (dictionary-store-state 'dictionary-do-select-strategy nil))
939
940(defun dictionary-do-select-strategy ()
941 "The workhorse for doing the strategy selection."
942
943 (message "Request existing matching algorithm")
944 (dictionary-send-command "show strat")
945
946 (let ((reply (dictionary-read-reply-and-split)))
947 (message nil)
948 (if (dictionary-check-reply reply 555)
949 (error "No strategies available")
950 (unless (dictionary-check-reply reply 111)
951 (error "Unknown server answer: %s"
952 (dictionary-reply reply)))
953 (dictionary-display-strategies reply))))
954
955(defun dictionary-display-strategies (reply)
956 "Handle the display of all strategies existing on the server"
957 (dictionary-pre-buffer)
958 (insert "Please select your default search strategy:\n\n")
959 (dictionary-display-strategy-line ". \"The servers default\"")
960 (let* ((reply (dictionary-read-answer))
961 (list (dictionary-simple-split-string reply "\n+")))
962 (mapc 'dictionary-display-strategy-line list))
963 (dictionary-post-buffer))
964
965(defun dictionary-display-strategy-line (string)
966 "Display a single strategy"
967 (let* ((list (dictionary-split-string string))
968 (strategy (car list))
969 (description (cadr list)))
970 (if strategy
971 (progn
972 (link-insert-link description 'dictionary-reference-face
973 'dictionary-set-strategy strategy
974 "Mouse-2 to select this matching algorithm")
975 (insert "\n")))))
976
977(defun dictionary-set-strategy (strategy &rest ignored)
978 "Select this strategy as new default"
979 (setq dictionary-default-strategy strategy)
980 (dictionary-restore-state)
981 (message "Strategy %s has been selected" strategy))
982
983(defun dictionary-new-matching (word)
984 "Run a new matching search on `word'."
985 (dictionary-ensure-buffer)
986 (dictionary-store-positions)
987 (dictionary-do-matching word dictionary-default-dictionary
988 dictionary-default-strategy
989 'dictionary-display-match-result)
990 (dictionary-store-state 'dictionary-do-matching
991 (list word dictionary-default-dictionary
992 dictionary-default-strategy
993 'dictionary-display-match-result)))
994
995(defun dictionary-do-matching (word dictionary strategy function)
996 "Ask the server about matches to `word' and display it."
997
998 (message "Lookup matching words for %s in %s using %s"
999 word dictionary strategy)
1000 (dictionary-send-command
1001 (concat "match " (dictionary-encode-charset dictionary "") " "
1002 (dictionary-encode-charset strategy "") " \""
1003 (dictionary-encode-charset word "") "\""))
1004 (let ((reply (dictionary-read-reply-and-split)))
1005 (message nil)
1006 (if (dictionary-check-reply reply 550)
1007 (error "Dictionary \"%s\" is invalid" dictionary))
1008 (if (dictionary-check-reply reply 551)
1009 (error "Strategy \"%s\" is invalid" strategy))
1010 (if (dictionary-check-reply reply 552)
1011 (error (concat
1012 "No match for \"%s\" with strategy \"%s\" in "
1013 "dictionary \"%s\".")
1014 word strategy dictionary))
1015 (unless (dictionary-check-reply reply 152)
1016 (error "Unknown server answer: %s" (dictionary-reply reply)))
1017 (funcall function reply)))
1018
1019(defun dictionary-display-only-match-result (reply)
1020 "Display the results from the current matches without the headers."
1021
1022 (let ((number (nth 1 (dictionary-reply-list reply)))
1023 (list (dictionary-simple-split-string (dictionary-read-answer) "\n+")))
1024 (insert number " matching word" (if (equal number "1") "" "s")
1025 " found\n\n")
1026 (let ((result nil))
1027 (mapc (lambda (item)
1028 (let* ((list (dictionary-split-string item))
1029 (dictionary (car list))
1030 (word (cadr list))
1031 (hash (assoc dictionary result)))
1032 (if dictionary
1033 (if hash
1034 (setcdr hash (cons word (cdr hash)))
1035 (setq result (cons
1036 (cons dictionary (list word))
1037 result))))))
1038 list)
1039 (dictionary-display-match-lines (reverse result)))))
1040
1041(defun dictionary-display-match-result (reply)
1042 "Display the results from the current matches."
1043 (dictionary-pre-buffer)
1044
1045 (let ((number (nth 1 (dictionary-reply-list reply)))
1046 (list (dictionary-simple-split-string (dictionary-read-answer) "\n+")))
1047 (insert number " matching word" (if (equal number "1") "" "s")
1048 " found\n\n")
1049 (let ((result nil))
1050 (mapc (lambda (item)
1051 (let* ((list (dictionary-split-string item))
1052 (dictionary (car list))
1053 (word (cadr list))
1054 (hash (assoc dictionary result)))
1055 (if dictionary
1056 (if hash
1057 (setcdr hash (cons word (cdr hash)))
1058 (setq result (cons
1059 (cons dictionary (list word))
1060 result))))))
1061 list)
1062 (dictionary-display-match-lines (reverse result))))
1063 (dictionary-post-buffer))
1064
1065(defun dictionary-display-match-lines (list)
1066 "Display the match lines."
1067 (mapc (lambda (item)
1068 (let ((dictionary (car item))
1069 (word-list (cdr item)))
1070 (insert "Matches from " dictionary ":\n")
1071 (mapc (lambda (word)
1072 (setq word (dictionary-decode-charset word dictionary))
1073 (insert " ")
1074 (link-insert-link word
1075 'dictionary-reference-face
1076 'dictionary-new-search
1077 (cons word dictionary)
1078 "Mouse-2 to lookup word")
1079 (insert "\n")) (reverse word-list))
1080 (insert "\n")))
1081 list))
1082
1083;; Returns a sensible default for dictionary-search:
1084;; - if region is active returns its contents
1085;; - otherwise return the word near the point
1086(defun dictionary-search-default ()
1087 (if (use-region-p)
1088 (buffer-substring-no-properties (region-beginning) (region-end))
1089 (current-word t)))
1090
1091;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1092;; User callable commands
1093;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1094
1095;;;###autoload
1096(defun dictionary-search (word &optional dictionary)
1097 "Search the `word' in `dictionary' if given or in all if nil.
1098It presents the word at point as default input and allows editing it."
1099 (interactive
1100 (list (let ((default (dictionary-search-default)))
1101 (read-string (if default
1102 (format "Search word (%s): " default)
1103 "Search word: ")
1104 nil nil default))
1105 (if current-prefix-arg
1106 (read-string (if dictionary-default-dictionary
1107 (format "Dictionary (%s): " dictionary-default-dictionary)
1108 "Dictionary: ")
1109 nil nil dictionary-default-dictionary)
1110 dictionary-default-dictionary)))
1111
1112 ;; if called by pressing the button
1113 (unless word
1114 (setq word (read-string "Search word: ")))
1115 ;; just in case non-interactivly called
1116 (unless dictionary
1117 (setq dictionary dictionary-default-dictionary))
1118 (dictionary-new-search (cons word dictionary)))
1119
1120;;;###autoload
1121(defun dictionary-lookup-definition ()
1122 "Unconditionally lookup the word at point."
1123 (interactive)
1124 (dictionary-new-search (cons (current-word) dictionary-default-dictionary)))
1125
1126(defun dictionary-previous ()
1127 "Go to the previous location in the current buffer"
1128 (interactive)
1129 (unless (dictionary-mode-p)
1130 (error "Current buffer is no dictionary buffer"))
1131 (dictionary-restore-state))
1132
1133(defun dictionary-next-link ()
1134 "Place the cursor to the next link."
1135 (interactive)
1136 (let ((pos (link-next-link)))
1137 (if pos
1138 (goto-char pos)
1139 (error "There is no next link"))))
1140
1141(defun dictionary-prev-link ()
1142 "Place the cursor to the previous link."
1143 (interactive)
1144 (let ((pos (link-prev-link)))
1145 (if pos
1146 (goto-char pos)
1147 (error "There is no previous link"))))
1148
1149(defun dictionary-help ()
1150 "Display a little help"
1151 (interactive)
1152 (describe-function 'dictionary-mode))
1153
1154;;;###autoload
1155(defun dictionary-match-words (&optional pattern &rest ignored)
1156 "Search `pattern' in current default dictionary using default strategy."
1157 (interactive)
1158 ;; can't use interactive because of mouse events
1159 (or pattern
1160 (setq pattern (read-string "Search pattern: ")))
1161 (dictionary-new-matching pattern))
1162
1163;;;###autoload
1164(defun dictionary-mouse-popup-matching-words (event)
1165 "Display entries matching the word at the cursor"
1166 (interactive "e")
1167 (let ((word (save-window-excursion
1168 (save-excursion
1169 (mouse-set-point event)
1170 (current-word)))))
1171 (selected-window)
1172 (dictionary-popup-matching-words word)))
1173
1174;;;###autoload
1175(defun dictionary-popup-matching-words (&optional word)
1176 "Display entries matching the word at the point"
1177 (interactive)
1178 (unless (functionp 'popup-menu)
1179 (error "Sorry, popup menus are not available in this emacs version"))
1180 (dictionary-do-matching (or word (current-word))
1181 dictionary-default-dictionary
1182 dictionary-default-popup-strategy
1183 'dictionary-process-popup-replies))
1184
1185(defun dictionary-process-popup-replies (reply)
1186 (let ((number (nth 1 (dictionary-reply-list reply)))
1187 (list (dictionary-simple-split-string (dictionary-read-answer) "\n+")))
1188
1189 (let ((result (mapcar (lambda (item)
1190 (let* ((list (dictionary-split-string item))
1191 (dictionary (car list))
1192 (word (dictionary-decode-charset
1193 (cadr list) dictionary)))
1194 (message word)
1195 (if (equal word "")
1196 [ "-" nil nil]
1197 (vector (concat "[" dictionary "] " word)
1198 `(dictionary-new-search
1199 '(,word . ,dictionary))
1200 t ))))
1201
1202 list)))
1203 (let ((menu (make-sparse-keymap 'dictionary-popup)))
1204
1205 (easy-menu-define dictionary-mode-map-menu dictionary-mode-map
1206 "Menu used for displaying dictionary popup"
1207 (cons "Matching words"
1208 `(,@result)))
1209 (popup-menu dictionary-mode-map-menu)))))
1210
1211;;; Tooltip support
1212
1213;; Common to GNU Emacs and XEmacs
1214
1215;; Add a mode indicater named "Dict"
1216(defvar dictionary-tooltip-mode
1217 nil
1218 "Indicates wheather the dictionary tooltip mode is active")
1219(nconc minor-mode-alist '((dictionary-tooltip-mode " Dict")))
1220
1221(defcustom dictionary-tooltip-dictionary
1222 nil
1223 "This dictionary to lookup words for tooltips"
1224 :group 'dictionary
1225 :type '(choice (const :tag "None" nil) string))
1226
1227(defun dictionary-definition (word &optional dictionary)
1228 (interactive)
1229 (unwind-protect
1230 (let ((dictionary (or dictionary dictionary-default-dictionary)))
1231 (dictionary-do-search word dictionary 'dictionary-read-definition t))
1232 nil))
1233
1234(defun dictionary-read-definition (reply)
1235 (let ((list (dictionary-simple-split-string (dictionary-read-answer) "\n+")))
1236 (mapconcat 'identity (cdr list) "\n")))
1237
1238(defconst dictionary-use-balloon-help
1239 (eval-when-compile
1240 (condition-case nil
1241 (require 'balloon-help)
1242 (error nil))))
1243
1244(make-variable-buffer-local 'dictionary-balloon-help-extent)
1245
1246(if dictionary-use-balloon-help
1247 (progn
1248
1249;; The following definition are only valid for XEmacs with balloon-help
1250
1251(defvar dictionary-balloon-help-position nil
1252 "Current position to lookup word")
1253
1254(defun dictionary-balloon-help-store-position (event)
1255 (setq dictionary-balloon-help-position (event-point event)))
1256
1257(defun dictionary-balloon-help-description (&rest extent)
1258 "Get the word from the cursor and lookup it"
1259 (if dictionary-balloon-help-position
1260 (let ((word (save-window-excursion
1261 (save-excursion
1262 (goto-char dictionary-balloon-help-position)
1263 (current-word)))))
1264 (let ((definition
1265 (dictionary-definition word dictionary-tooltip-dictionary)))
1266 (if definition
1267 (dictionary-decode-charset definition
1268 dictionary-tooltip-dictionary)
1269 nil)))))
1270
1271(defvar dictionary-balloon-help-extent nil
1272 "The extent for activating the balloon help")
1273
1274;;;###autoload
1275(defun dictionary-tooltip-mode (&optional arg)
1276 "Display tooltips for the current word"
1277 (interactive "P")
1278 (let* ((on (if arg
1279 (> (prefix-numeric-value arg) 0)
1280 (not dictionary-tooltip-mode))))
1281 (make-local-variable 'dictionary-tooltip-mode)
1282 (if on
1283 ;; active mode
1284 (progn
1285 ;; remove old extend
1286 (if dictionary-balloon-help-extent
1287 (delete-extent dictionary-balloon-help-extent))
1288 ;; create new one
1289 (setq dictionary-balloon-help-extent (make-extent (point-min)
1290 (point-max)))
1291 (set-extent-property dictionary-balloon-help-extent
1292 'balloon-help
1293 'dictionary-balloon-help-description)
1294 (set-extent-property dictionary-balloon-help-extent
1295 'start-open nil)
1296 (set-extent-property dictionary-balloon-help-extent
1297 'end-open nil)
1298 (add-hook 'mouse-motion-hook
1299 'dictionary-balloon-help-store-position))
1300
1301 ;; deactivate mode
1302 (if dictionary-balloon-help-extent
1303 (delete-extent dictionary-balloon-help-extent))
1304 (remove-hook 'mouse-motion-hook
1305 'dictionary-balloon-help-store-position))
1306 (setq dictionary-tooltip-mode on)
1307 (balloon-help-minor-mode on)))
1308
1309) ;; end of XEmacs part
1310
1311(defvar global-dictionary-tooltip-mode
1312 nil)
1313
1314;;; Tooltip support for GNU Emacs
1315(defun dictionary-display-tooltip (event)
1316 "Search the current word in the `dictionary-tooltip-dictionary'."
1317 (interactive "e")
1318 (if dictionary-tooltip-dictionary
1319 (let ((word (save-window-excursion
1320 (save-excursion
1321 (mouse-set-point event)
1322 (current-word)))))
1323 (let ((definition
1324 (dictionary-definition word dictionary-tooltip-dictionary)))
1325 (if definition
1326 (tooltip-show
1327 (dictionary-decode-charset definition
1328 dictionary-tooltip-dictionary)))
1329 t))
1330 nil))
1331
1332;;;###autoload
1333(defun dictionary-tooltip-mode (&optional arg)
1334 "Display tooltips for the current word"
1335 (interactive "P")
1336 (require 'tooltip)
1337 (let ((on (if arg
1338 (> (prefix-numeric-value arg) 0)
1339 (not dictionary-tooltip-mode))))
1340 (make-local-variable 'dictionary-tooltip-mode)
1341 (setq dictionary-tooltip-mode on)
1342 ;; make sure that tooltip is still (global available) even is on
1343 ;; if nil
1344 (tooltip-mode 1)
1345 (add-hook 'tooltip-hook 'dictionary-display-tooltip)
1346 (make-local-variable 'track-mouse)
1347 (setq track-mouse on)))
1348
1349;;;###autoload
1350(defun global-dictionary-tooltip-mode (&optional arg)
1351 "Enable/disable dictionary-tooltip-mode for all buffers"
1352 (interactive "P")
1353 (require 'tooltip)
1354 (let* ((on (if arg (> (prefix-numeric-value arg) 0)
1355 (not global-dictionary-tooltip-mode)))
1356 (hook-fn (if on 'add-hook 'remove-hook)))
1357 (setq global-dictionary-tooltip-mode on)
1358 (tooltip-mode 1)
1359 (funcall hook-fn 'tooltip-hook 'dictionary-display-tooltip)
1360 (setq-default dictionary-tooltip-mode on)
1361 (setq-default track-mouse on)))
1362
1363) ;; end of GNU Emacs part
1364
1365(provide 'dictionary)
1366
1367;;; dictionary.el ends here
diff --git a/lisp/net/link.el b/lisp/net/link.el
new file mode 100644
index 00000000000..30eadb10176
--- /dev/null
+++ b/lisp/net/link.el
@@ -0,0 +1,129 @@
1;;; link.el --- Hypertext links in text buffers
2
3;; Author: Torsten Hilbrich <torsten.hilbrich@gmx.net>
4;; Keywords: interface, hypermedia
5;; Version: 1.11
6
7;; This file is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation; either version 2, or (at your option)
10;; any later version.
11
12;; This file is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs; see the file COPYING. If not, write to
19;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20;; Boston, MA 02111-1307, USA.
21
22;;; Commentary:
23
24;; This file contains functions for using links in buffers. A link is
25;; a part of the buffer marked with a special face, beeing
26;; hightlighted while the mouse points to it and beeing activated when
27;; pressing return or clicking the button2.
28
29;; Which each link a function and some data are associated. Upon
30;; clicking the function is called with the data as only
31;; argument. Both the function and the data are stored in text
32;; properties.
33;;
34;; link-create-link - insert a new link for the text in the given range
35;; link-initialize-keymap - install the keybinding for selecting links
36
37;;; Code:
38
39(eval-when-compile
40 (require 'cl))
41
42(defun link-create-link (start end face function &optional data help)
43 "Create a link in the current buffer starting from `start' going to `end'.
44The `face' is used for displaying, the `data' are stored together with the
45link. Upon clicking the `function' is called with `data' as argument."
46 (let ((properties `(face ,face
47 mouse-face highlight
48 link t
49 link-data ,data
50 help-echo ,help
51 link-function ,function)))
52 (remove-text-properties start end properties)
53 (add-text-properties start end properties)))
54
55(defun link-insert-link (text face function &optional data help)
56 "Insert the `text' at point to be formatted as link.
57The `face' is used for displaying, the `data' are stored together with the
58link. Upon clicking the `function' is called with `data' as argument."
59 (let ((start (point)))
60 (insert text)
61 (link-create-link start (point) face function data help)))
62
63(defun link-selected (&optional all)
64 "Is called upon clicking or otherwise visiting the link."
65 (interactive)
66
67 (let* ((properties (text-properties-at (point)))
68 (function (plist-get properties 'link-function))
69 (data (plist-get properties 'link-data)))
70 (if function
71 (funcall function data all))))
72
73(defun link-selected-all ()
74 "Called for meta clicking the link"
75 (interactive)
76 (link-selected 'all))
77
78(defun link-mouse-click (event &optional all)
79 "Is called upon clicking the link."
80 (interactive "@e")
81
82 (mouse-set-point event)
83 (link-selected))
84
85(defun link-mouse-click-all (event)
86 "Is called upon meta clicking the link."
87 (interactive "@e")
88
89 (mouse-set-point event)
90 (link-selected-all))
91
92(defun link-next-link ()
93 "Return the position of the next link or nil if there is none"
94 (let* ((pos (point))
95 (pos (next-single-property-change pos 'link)))
96 (if pos
97 (if (text-property-any pos (min (1+ pos) (point-max)) 'link t)
98 pos
99 (next-single-property-change pos 'link))
100 nil)))
101
102
103(defun link-prev-link ()
104 "Return the position of the previous link or nil if there is none"
105 (let* ((pos (point))
106 (pos (previous-single-property-change pos 'link)))
107 (if pos
108 (if (text-property-any pos (1+ pos) 'link t)
109 pos
110 (let ((val (previous-single-property-change pos 'link)))
111 (if val
112 val
113 (text-property-any (point-min) (1+ (point-min)) 'link t))))
114 nil)))
115
116(defun link-initialize-keymap (keymap)
117 "Defines the necessary bindings inside keymap"
118
119 (if (and (boundp 'running-xemacs) running-xemacs)
120 (progn
121 (define-key keymap [button2] 'link-mouse-click)
122 (define-key keymap [(meta button2)] 'link-mouse-click-all))
123 (define-key keymap [mouse-2] 'link-mouse-click)
124 (define-key keymap [M-mouse-2] 'link-mouse-click-all))
125 (define-key keymap "\r" 'link-selected)
126 (define-key keymap "\M-\r" 'link-selected-all))
127
128(provide 'link)
129;;; link.el ends here