38412
|
1 ;;; thingatpt.el --- get the `thing' at point
|
4934
|
2
|
74442
|
3 ;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2000,
|
75347
|
4 ;; 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
|
4934
|
5
|
|
6 ;; Author: Mike Williams <mikew@gopher.dosli.govt.nz>
|
29516
|
7 ;; Maintainer: FSF
|
5140
|
8 ;; Keywords: extensions, matching, mouse
|
4934
|
9 ;; Created: Thu Mar 28 13:48:23 1991
|
|
10
|
|
11 ;; This file is part of GNU Emacs.
|
|
12
|
|
13 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
14 ;; it under the terms of the GNU General Public License as published by
|
|
15 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
16 ;; any later version.
|
|
17
|
|
18 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
21 ;; GNU General Public License for more details.
|
|
22
|
7938
|
23 ;;; Commentary:
|
14169
|
24
|
16629
|
25 ;; This file provides routines for getting the "thing" at the location of
|
|
26 ;; point, whatever that "thing" happens to be. The "thing" is defined by
|
16427
|
27 ;; its beginning and end positions in the buffer.
|
4934
|
28 ;;
|
|
29 ;; The function bounds-of-thing-at-point finds the beginning and end
|
16629
|
30 ;; positions by moving first forward to the end of the "thing", and then
|
4934
|
31 ;; backwards to the beginning. By default, it uses the corresponding
|
16629
|
32 ;; forward-"thing" operator (eg. forward-word, forward-line).
|
4934
|
33 ;;
|
|
34 ;; Special cases are allowed for using properties associated with the named
|
49597
|
35 ;; "thing":
|
4934
|
36 ;;
|
16629
|
37 ;; forward-op Function to call to skip forward over a "thing" (or
|
4934
|
38 ;; with a negative argument, backward).
|
49597
|
39 ;;
|
16629
|
40 ;; beginning-op Function to call to skip to the beginning of a "thing".
|
|
41 ;; end-op Function to call to skip to the end of a "thing".
|
4934
|
42 ;;
|
|
43 ;; Reliance on existing operators means that many `things' can be accessed
|
|
44 ;; without further code: eg.
|
|
45 ;; (thing-at-point 'line)
|
|
46 ;; (thing-at-point 'page)
|
|
47
|
14169
|
48 ;;; Code:
|
4934
|
49
|
|
50 (provide 'thingatpt)
|
|
51
|
14169
|
52 ;; Basic movement
|
4934
|
53
|
|
54 ;;;###autoload
|
16629
|
55 (defun forward-thing (thing &optional n)
|
71614
|
56 "Move forward to the end of the Nth next THING."
|
16629
|
57 (let ((forward-op (or (get thing 'forward-op)
|
|
58 (intern-soft (format "forward-%s" thing)))))
|
29516
|
59 (if (functionp forward-op)
|
16629
|
60 (funcall forward-op (or n 1))
|
|
61 (error "Can't determine how to move over a %s" thing))))
|
4934
|
62
|
14169
|
63 ;; General routines
|
4934
|
64
|
|
65 ;;;###autoload
|
16629
|
66 (defun bounds-of-thing-at-point (thing)
|
|
67 "Determine the start and end buffer locations for the THING at point.
|
|
68 THING is a symbol which specifies the kind of syntactic entity you want.
|
|
69 Possibilities include `symbol', `list', `sexp', `defun', `filename', `url',
|
|
70 `word', `sentence', `whitespace', `line', `page' and others.
|
|
71
|
|
72 See the file `thingatpt.el' for documentation on how to define
|
|
73 a symbol as a valid THING.
|
|
74
|
|
75 The value is a cons cell (START . END) giving the start and end positions
|
|
76 of the textual entity that was found."
|
18610
|
77 (if (get thing 'bounds-of-thing-at-point)
|
|
78 (funcall (get thing 'bounds-of-thing-at-point))
|
|
79 (let ((orig (point)))
|
|
80 (condition-case nil
|
|
81 (save-excursion
|
|
82 ;; Try moving forward, then back.
|
71614
|
83 (funcall ;; First move to end.
|
|
84 (or (get thing 'end-op)
|
|
85 (lambda () (forward-thing thing 1))))
|
|
86 (funcall ;; Then move to beg.
|
|
87 (or (get thing 'beginning-op)
|
|
88 (lambda () (forward-thing thing -1))))
|
|
89 (let ((beg (point)))
|
18610
|
90 (if (not (and beg (> beg orig)))
|
|
91 ;; If that brings us all the way back to ORIG,
|
|
92 ;; it worked. But END may not be the real end.
|
|
93 ;; So find the real end that corresponds to BEG.
|
|
94 (let ((real-end
|
49597
|
95 (progn
|
|
96 (funcall
|
|
97 (or (get thing 'end-op)
|
71614
|
98 (lambda () (forward-thing thing 1))))
|
18610
|
99 (point))))
|
|
100 (if (and beg real-end (<= beg orig) (<= orig real-end))
|
|
101 (cons beg real-end)))
|
|
102 (goto-char orig)
|
|
103 ;; Try a second time, moving backward first and then forward,
|
|
104 ;; so that we can find a thing that ends at ORIG.
|
71614
|
105 (funcall ;; First, move to beg.
|
|
106 (or (get thing 'beginning-op)
|
|
107 (lambda () (forward-thing thing -1))))
|
|
108 (funcall ;; Then move to end.
|
|
109 (or (get thing 'end-op)
|
|
110 (lambda () (forward-thing thing 1))))
|
|
111 (let ((end (point))
|
|
112 (real-beg
|
49597
|
113 (progn
|
|
114 (funcall
|
|
115 (or (get thing 'beginning-op)
|
71614
|
116 (lambda () (forward-thing thing -1))))
|
18610
|
117 (point))))
|
|
118 (if (and real-beg end (<= real-beg orig) (<= orig end))
|
|
119 (cons real-beg end))))))
|
|
120 (error nil)))))
|
4934
|
121
|
|
122 ;;;###autoload
|
16629
|
123 (defun thing-at-point (thing)
|
|
124 "Return the THING at point.
|
|
125 THING is a symbol which specifies the kind of syntactic entity you want.
|
|
126 Possibilities include `symbol', `list', `sexp', `defun', `filename', `url',
|
|
127 `word', `sentence', `whitespace', `line', `page' and others.
|
|
128
|
|
129 See the file `thingatpt.el' for documentation on how to define
|
|
130 a symbol as a valid THING."
|
18610
|
131 (if (get thing 'thing-at-point)
|
|
132 (funcall (get thing 'thing-at-point))
|
|
133 (let ((bounds (bounds-of-thing-at-point thing)))
|
49597
|
134 (if bounds
|
18610
|
135 (buffer-substring (car bounds) (cdr bounds))))))
|
4934
|
136
|
14169
|
137 ;; Go to beginning/end
|
4934
|
138
|
16629
|
139 (defun beginning-of-thing (thing)
|
|
140 (let ((bounds (bounds-of-thing-at-point thing)))
|
|
141 (or bounds (error "No %s here" thing))
|
4934
|
142 (goto-char (car bounds))))
|
|
143
|
16629
|
144 (defun end-of-thing (thing)
|
|
145 (let ((bounds (bounds-of-thing-at-point thing)))
|
|
146 (or bounds (error "No %s here" thing))
|
4934
|
147 (goto-char (cdr bounds))))
|
|
148
|
49597
|
149 ;; Special cases
|
4934
|
150
|
49597
|
151 ;; Lines
|
9931
|
152
|
|
153 ;; bolp will be false when you click on the last line in the buffer
|
|
154 ;; and it has no final newline.
|
|
155
|
|
156 (put 'line 'beginning-op
|
71614
|
157 (lambda () (if (bolp) (forward-line -1) (beginning-of-line))))
|
9931
|
158
|
49597
|
159 ;; Sexps
|
4934
|
160
|
|
161 (defun in-string-p ()
|
|
162 (let ((orig (point)))
|
|
163 (save-excursion
|
|
164 (beginning-of-defun)
|
|
165 (nth 3 (parse-partial-sexp (point) orig)))))
|
|
166
|
|
167 (defun end-of-sexp ()
|
|
168 (let ((char-syntax (char-syntax (char-after (point)))))
|
|
169 (if (or (eq char-syntax ?\))
|
|
170 (and (eq char-syntax ?\") (in-string-p)))
|
|
171 (forward-char 1)
|
|
172 (forward-sexp 1))))
|
|
173
|
|
174 (put 'sexp 'end-op 'end-of-sexp)
|
|
175
|
18432
|
176 (defun beginning-of-sexp ()
|
|
177 (let ((char-syntax (char-syntax (char-before (point)))))
|
|
178 (if (or (eq char-syntax ?\()
|
|
179 (and (eq char-syntax ?\") (in-string-p)))
|
|
180 (forward-char -1)
|
|
181 (forward-sexp -1))))
|
|
182
|
|
183 (put 'sexp 'beginning-op 'beginning-of-sexp)
|
|
184
|
49597
|
185 ;; Lists
|
4934
|
186
|
71614
|
187 (put 'list 'end-op (lambda () (up-list 1)))
|
4934
|
188 (put 'list 'beginning-op 'backward-sexp)
|
|
189
|
49270
|
190 ;; Filenames and URLs www.com/foo%32bar
|
4934
|
191
|
49486
|
192 (defvar thing-at-point-file-name-chars "-~/[:alnum:]_.${}#%,:"
|
4934
|
193 "Characters allowable in filenames.")
|
|
194
|
49597
|
195 (put 'filename 'end-op
|
49486
|
196 (lambda ()
|
|
197 (re-search-forward (concat "\\=[" thing-at-point-file-name-chars "]*")
|
|
198 nil t)))
|
4934
|
199 (put 'filename 'beginning-op
|
49486
|
200 (lambda ()
|
|
201 (if (re-search-backward (concat "[^" thing-at-point-file-name-chars "]")
|
|
202 nil t)
|
|
203 (forward-char)
|
|
204 (goto-char (point-min)))))
|
16629
|
205
|
18610
|
206 (defvar thing-at-point-url-path-regexp
|
|
207 "[^]\t\n \"'()<>[^`{}]*[^]\t\n \"'()<>[^`{}.,;]+"
|
47786
|
208 "A regular expression probably matching the host and filename or e-mail part of a URL.")
|
18610
|
209
|
|
210 (defvar thing-at-point-short-url-regexp
|
|
211 (concat "[-A-Za-z0-9.]+" thing-at-point-url-path-regexp)
|
|
212 "A regular expression probably matching a URL without an access scheme.
|
|
213 Hostname matching is stricter in this case than for
|
|
214 ``thing-at-point-url-regexp''.")
|
|
215
|
47769
|
216 (defvar thing-at-point-uri-schemes
|
74109
|
217 ;; Officials from http://www.iana.org/assignments/uri-schemes.html
|
47769
|
218 '("ftp://" "http://" "gopher://" "mailto:" "news:" "nntp:"
|
|
219 "telnet://" "wais://" "file:/" "prospero:" "z39.50s:" "z39.50r:"
|
|
220 "cid:" "mid:" "vemmi:" "service:" "imap:" "nfs:" "acap:" "rtsp:"
|
|
221 "tip:" "pop:" "data:" "dav:" "opaquelocktoken:" "sip:" "tel:" "fax:"
|
|
222 "modem:" "ldap:" "https://" "soap.beep:" "soap.beeps:" "urn:" "go:"
|
|
223 "afs:" "tn3270:" "mailserver:"
|
74109
|
224 "crid:" "dict:" "dns:" "dtn:" "h323:" "im:" "info:" "ipp:"
|
|
225 "iris.beep:" "mtqp:" "mupdate:" "pres:" "sips:" "snmp:" "tag:"
|
|
226 "tftp:" "xmlrpc.beep:" "xmlrpc.beeps:" "xmpp:"
|
47769
|
227 ;; Compatibility
|
74109
|
228 "snews:" "irc:" "mms://" "mmsh://")
|
71614
|
229 "Uniform Resource Identifier (URI) Schemes.")
|
47769
|
230
|
18610
|
231 (defvar thing-at-point-url-regexp
|
47769
|
232 (concat "\\<\\(" (mapconcat 'identity thing-at-point-uri-schemes "\\|") "\\)"
|
|
233 thing-at-point-url-path-regexp)
|
18610
|
234 "A regular expression probably matching a complete URL.")
|
|
235
|
|
236 (defvar thing-at-point-markedup-url-regexp
|
|
237 "<URL:[^>]+>"
|
|
238 "A regular expression matching a URL marked up per RFC1738.
|
|
239 This may contain whitespace (including newlines) .")
|
|
240
|
|
241 (put 'url 'bounds-of-thing-at-point 'thing-at-point-bounds-of-url-at-point)
|
|
242 (defun thing-at-point-bounds-of-url-at-point ()
|
71614
|
243 (let ((strip (thing-at-point-looking-at
|
|
244 thing-at-point-markedup-url-regexp))) ;; (url "") short
|
|
245 (if (or strip
|
72849
1302db63b66b
(thing-at-point-bounds-of-url-at-point): Delete spurious backquote.
Richard M. Stallman <rms@gnu.org>
diff
changeset
|
246 (thing-at-point-looking-at thing-at-point-url-regexp)
|
18610
|
247 ;; Access scheme omitted?
|
71614
|
248 ;; (setq short (thing-at-point-looking-at
|
|
249 ;; thing-at-point-short-url-regexp))
|
|
250 )
|
18610
|
251 (let ((beginning (match-beginning 0))
|
|
252 (end (match-end 0)))
|
71614
|
253 (when strip
|
|
254 (setq beginning (+ beginning 5))
|
|
255 (setq end (- end 1)))
|
18610
|
256 (cons beginning end)))))
|
16629
|
257
|
18610
|
258 (put 'url 'thing-at-point 'thing-at-point-url-at-point)
|
|
259 (defun thing-at-point-url-at-point ()
|
|
260 "Return the URL around or before point.
|
20982
|
261
|
|
262 Search backwards for the start of a URL ending at or after point. If
|
|
263 no URL found, return nil. The access scheme will be prepended if
|
|
264 absent: \"mailto:\" if the string contains \"@\", \"ftp://\" if it
|
|
265 starts with \"ftp\" and not \"ftp:/\", or \"http://\" by default."
|
|
266
|
18610
|
267 (let ((url "") short strip)
|
|
268 (if (or (setq strip (thing-at-point-looking-at
|
|
269 thing-at-point-markedup-url-regexp))
|
|
270 (thing-at-point-looking-at thing-at-point-url-regexp)
|
|
271 ;; Access scheme omitted?
|
|
272 (setq short (thing-at-point-looking-at
|
|
273 thing-at-point-short-url-regexp)))
|
|
274 (progn
|
|
275 (setq url (buffer-substring-no-properties (match-beginning 0)
|
|
276 (match-end 0)))
|
|
277 (and strip (setq url (substring url 5 -1))) ; Drop "<URL:" & ">"
|
|
278 ;; strip whitespace
|
23764
|
279 (while (string-match "[ \t\n\r]+" url)
|
18610
|
280 (setq url (replace-match "" t t url)))
|
74109
|
281 (and short (setq url (concat (cond ((string-match "^[a-zA-Z]+:" url)
|
|
282 ;; already has a URL scheme.
|
|
283 "")
|
|
284 ((string-match "@" url)
|
20982
|
285 "mailto:")
|
|
286 ;; e.g. ftp.swiss... or ftp-swiss...
|
|
287 ((string-match "^ftp" url)
|
|
288 "ftp://")
|
|
289 (t "http://"))
|
|
290 url)))
|
18610
|
291 (if (string-equal "" url)
|
|
292 nil
|
|
293 url)))))
|
|
294
|
|
295 ;; The normal thingatpt mechanism doesn't work for complex regexps.
|
|
296 ;; This should work for almost any regexp wherever we are in the
|
|
297 ;; match. To do a perfect job for any arbitrary regexp would mean
|
|
298 ;; testing every position before point. Regexp searches won't find
|
|
299 ;; matches that straddle the start position so we search forwards once
|
|
300 ;; and then back repeatedly and then back up a char at a time.
|
|
301
|
|
302 (defun thing-at-point-looking-at (regexp)
|
|
303 "Return non-nil if point is in or just after a match for REGEXP.
|
|
304 Set the match data from the earliest such match ending at or after
|
|
305 point."
|
|
306 (save-excursion
|
|
307 (let ((old-point (point)) match)
|
|
308 (and (looking-at regexp)
|
|
309 (>= (match-end 0) old-point)
|
|
310 (setq match (point)))
|
|
311 ;; Search back repeatedly from end of next match.
|
|
312 ;; This may fail if next match ends before this match does.
|
|
313 (re-search-forward regexp nil 'limit)
|
|
314 (while (and (re-search-backward regexp nil t)
|
|
315 (or (> (match-beginning 0) old-point)
|
|
316 (and (looking-at regexp) ; Extend match-end past search start
|
|
317 (>= (match-end 0) old-point)
|
|
318 (setq match (point))))))
|
|
319 (if (not match) nil
|
|
320 (goto-char match)
|
|
321 ;; Back up a char at a time in case search skipped
|
|
322 ;; intermediate match straddling search start pos.
|
|
323 (while (and (not (bobp))
|
|
324 (progn (backward-char 1) (looking-at regexp))
|
|
325 (>= (match-end 0) old-point)
|
|
326 (setq match (point))))
|
|
327 (goto-char match)
|
|
328 (looking-at regexp)))))
|
|
329
|
18682
|
330 (put 'url 'end-op
|
71614
|
331 (lambda ()
|
|
332 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
|
333 (if bounds
|
|
334 (goto-char (cdr bounds))
|
|
335 (error "No URL here")))))
|
16629
|
336 (put 'url 'beginning-op
|
71614
|
337 (lambda ()
|
|
338 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
|
339 (if bounds
|
|
340 (goto-char (car bounds))
|
|
341 (error "No URL here")))))
|
4934
|
342
|
49597
|
343 ;; Whitespace
|
4934
|
344
|
16629
|
345 (defun forward-whitespace (arg)
|
4934
|
346 (interactive "p")
|
49597
|
347 (if (natnump arg)
|
17790
|
348 (re-search-forward "[ \t]+\\|\n" nil 'move arg)
|
16629
|
349 (while (< arg 0)
|
17790
|
350 (if (re-search-backward "[ \t]+\\|\n" nil 'move)
|
4934
|
351 (or (eq (char-after (match-beginning 0)) 10)
|
|
352 (skip-chars-backward " \t")))
|
16629
|
353 (setq arg (1+ arg)))))
|
4934
|
354
|
49597
|
355 ;; Buffer
|
4934
|
356
|
29592
|
357 (put 'buffer 'end-op (lambda () (goto-char (point-max))))
|
|
358 (put 'buffer 'beginning-op (lambda () (goto-char (point-min))))
|
4934
|
359
|
49597
|
360 ;; Symbols
|
4934
|
361
|
16629
|
362 (defun forward-symbol (arg)
|
4934
|
363 (interactive "p")
|
49597
|
364 (if (natnump arg)
|
17790
|
365 (re-search-forward "\\(\\sw\\|\\s_\\)+" nil 'move arg)
|
16629
|
366 (while (< arg 0)
|
17790
|
367 (if (re-search-backward "\\(\\sw\\|\\s_\\)+" nil 'move)
|
4934
|
368 (skip-syntax-backward "w_"))
|
16629
|
369 (setq arg (1+ arg)))))
|
4934
|
370
|
49597
|
371 ;; Syntax blocks
|
12593
|
372
|
|
373 (defun forward-same-syntax (&optional arg)
|
|
374 (interactive "p")
|
|
375 (while (< arg 0)
|
49597
|
376 (skip-syntax-backward
|
12593
|
377 (char-to-string (char-syntax (char-after (1- (point))))))
|
|
378 (setq arg (1+ arg)))
|
|
379 (while (> arg 0)
|
|
380 (skip-syntax-forward (char-to-string (char-syntax (char-after (point)))))
|
|
381 (setq arg (1- arg))))
|
|
382
|
49597
|
383 ;; Aliases
|
4934
|
384
|
|
385 (defun word-at-point () (thing-at-point 'word))
|
|
386 (defun sentence-at-point () (thing-at-point 'sentence))
|
|
387
|
16629
|
388 (defun read-from-whole-string (str)
|
71614
|
389 "Read a Lisp expression from STR.
|
16629
|
390 Signal an error if the entire string was not used."
|
|
391 (let* ((read-data (read-from-string str))
|
49597
|
392 (more-left
|
4934
|
393 (condition-case nil
|
47862
|
394 ;; The call to `ignore' suppresses a compiler warning.
|
47861
|
395 (progn (ignore (read-from-string (substring str (cdr read-data))))
|
4934
|
396 t)
|
|
397 (end-of-file nil))))
|
|
398 (if more-left
|
|
399 (error "Can't read whole string")
|
|
400 (car read-data))))
|
|
401
|
49597
|
402 (defun form-at-point (&optional thing pred)
|
|
403 (let ((sexp (condition-case nil
|
16629
|
404 (read-from-whole-string (thing-at-point (or thing 'sexp)))
|
4934
|
405 (error nil))))
|
16629
|
406 (if (or (not pred) (funcall pred sexp)) sexp)))
|
4934
|
407
|
27581
|
408 ;;;###autoload
|
4934
|
409 (defun sexp-at-point () (form-at-point 'sexp))
|
27581
|
410 ;;;###autoload
|
71614
|
411 (defun symbol-at-point ()
|
|
412 (let ((thing (thing-at-point 'symbol)))
|
|
413 (if thing (intern thing))))
|
27581
|
414 ;;;###autoload
|
4934
|
415 (defun number-at-point () (form-at-point 'sexp 'numberp))
|
27581
|
416 ;;;###autoload
|
4934
|
417 (defun list-at-point () (form-at-point 'list 'listp))
|
|
418
|
71614
|
419 ;; arch-tag: bb65a163-dae2-4055-aedc-fe11f497f698
|
38412
|
420 ;;; thingatpt.el ends here
|