* thingatpt.el (end-of-sexp): Fix bug#13952.
[bpt/emacs.git] / lisp / thingatpt.el
CommitLineData
55535639 1;;; thingatpt.el --- get the `thing' at point
1a2b6c52 2
ab422c4d 3;; Copyright (C) 1991-1998, 2000-2013 Free Software Foundation, Inc.
1a2b6c52
RS
4
5;; Author: Mike Williams <mikew@gopher.dosli.govt.nz>
6254fc9f 6;; Maintainer: FSF
b7f66977 7;; Keywords: extensions, matching, mouse
1a2b6c52 8;; Created: Thu Mar 28 13:48:23 1991
1a2b6c52
RS
9
10;; This file is part of GNU Emacs.
11
eb3fa2cf 12;; GNU Emacs is free software: you can redistribute it and/or modify
1a2b6c52 13;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
1a2b6c52
RS
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
eb3fa2cf
GM
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24
69f9ba7e 25;;; Commentary:
b578f267 26
c851323f
RS
27;; This file provides routines for getting the "thing" at the location of
28;; point, whatever that "thing" happens to be. The "thing" is defined by
7a8f27db 29;; its beginning and end positions in the buffer.
1a2b6c52
RS
30;;
31;; The function bounds-of-thing-at-point finds the beginning and end
c851323f 32;; positions by moving first forward to the end of the "thing", and then
1a2b6c52 33;; backwards to the beginning. By default, it uses the corresponding
c851323f 34;; forward-"thing" operator (eg. forward-word, forward-line).
1a2b6c52
RS
35;;
36;; Special cases are allowed for using properties associated with the named
f1180544 37;; "thing":
1a2b6c52 38;;
c851323f 39;; forward-op Function to call to skip forward over a "thing" (or
1a2b6c52 40;; with a negative argument, backward).
f1180544 41;;
c851323f
RS
42;; beginning-op Function to call to skip to the beginning of a "thing".
43;; end-op Function to call to skip to the end of a "thing".
1a2b6c52
RS
44;;
45;; Reliance on existing operators means that many `things' can be accessed
46;; without further code: eg.
47;; (thing-at-point 'line)
48;; (thing-at-point 'page)
49
b578f267 50;;; Code:
1a2b6c52
RS
51
52(provide 'thingatpt)
53
b578f267 54;; Basic movement
1a2b6c52
RS
55
56;;;###autoload
c851323f 57(defun forward-thing (thing &optional n)
f5bd0689
CY
58 "Move forward to the end of the Nth next THING.
59THING should be a symbol specifying a type of syntactic entity.
60Possibilities include `symbol', `list', `sexp', `defun',
61`filename', `url', `email', `word', `sentence', `whitespace',
62`line', and `page'."
c851323f
RS
63 (let ((forward-op (or (get thing 'forward-op)
64 (intern-soft (format "forward-%s" thing)))))
6254fc9f 65 (if (functionp forward-op)
c851323f
RS
66 (funcall forward-op (or n 1))
67 (error "Can't determine how to move over a %s" thing))))
1a2b6c52 68
b578f267 69;; General routines
1a2b6c52
RS
70
71;;;###autoload
c851323f
RS
72(defun bounds-of-thing-at-point (thing)
73 "Determine the start and end buffer locations for the THING at point.
f5bd0689
CY
74THING should be a symbol specifying a type of syntactic entity.
75Possibilities include `symbol', `list', `sexp', `defun',
76`filename', `url', `email', `word', `sentence', `whitespace',
77`line', and `page'.
c851323f 78
f5bd0689
CY
79See the file `thingatpt.el' for documentation on how to define a
80valid THING.
c851323f 81
f5bd0689
CY
82Return a cons cell (START . END) giving the start and end
83positions of the thing found."
d9cc804b
RS
84 (if (get thing 'bounds-of-thing-at-point)
85 (funcall (get thing 'bounds-of-thing-at-point))
86 (let ((orig (point)))
87 (condition-case nil
88 (save-excursion
89 ;; Try moving forward, then back.
2a59b30d
SM
90 (funcall ;; First move to end.
91 (or (get thing 'end-op)
92 (lambda () (forward-thing thing 1))))
93 (funcall ;; Then move to beg.
94 (or (get thing 'beginning-op)
95 (lambda () (forward-thing thing -1))))
96 (let ((beg (point)))
f278f87f 97 (if (<= beg orig)
d9cc804b
RS
98 ;; If that brings us all the way back to ORIG,
99 ;; it worked. But END may not be the real end.
100 ;; So find the real end that corresponds to BEG.
f278f87f 101 ;; FIXME: in which cases can `real-end' differ from `end'?
d9cc804b 102 (let ((real-end
f1180544
JB
103 (progn
104 (funcall
105 (or (get thing 'end-op)
2a59b30d 106 (lambda () (forward-thing thing 1))))
d9cc804b 107 (point))))
f278f87f
SM
108 (when (and (<= orig real-end) (< beg real-end))
109 (cons beg real-end)))
d9cc804b
RS
110 (goto-char orig)
111 ;; Try a second time, moving backward first and then forward,
112 ;; so that we can find a thing that ends at ORIG.
2a59b30d
SM
113 (funcall ;; First, move to beg.
114 (or (get thing 'beginning-op)
115 (lambda () (forward-thing thing -1))))
116 (funcall ;; Then move to end.
117 (or (get thing 'end-op)
118 (lambda () (forward-thing thing 1))))
119 (let ((end (point))
120 (real-beg
f1180544
JB
121 (progn
122 (funcall
123 (or (get thing 'beginning-op)
2a59b30d 124 (lambda () (forward-thing thing -1))))
d9cc804b 125 (point))))
f278f87f 126 (if (and (<= real-beg orig) (<= orig end) (< real-beg end))
d9cc804b
RS
127 (cons real-beg end))))))
128 (error nil)))))
1a2b6c52
RS
129
130;;;###autoload
c851323f
RS
131(defun thing-at-point (thing)
132 "Return the THING at point.
f5bd0689
CY
133THING should be a symbol specifying a type of syntactic entity.
134Possibilities include `symbol', `list', `sexp', `defun',
135`filename', `url', `email', `word', `sentence', `whitespace',
e84cad57 136`line', `number', and `page'.
c851323f
RS
137
138See the file `thingatpt.el' for documentation on how to define
139a symbol as a valid THING."
d9cc804b
RS
140 (if (get thing 'thing-at-point)
141 (funcall (get thing 'thing-at-point))
142 (let ((bounds (bounds-of-thing-at-point thing)))
f1180544 143 (if bounds
d9cc804b 144 (buffer-substring (car bounds) (cdr bounds))))))
1a2b6c52 145
b578f267 146;; Go to beginning/end
1a2b6c52 147
c851323f 148(defun beginning-of-thing (thing)
f5bd0689
CY
149 "Move point to the beginning of THING.
150The bounds of THING are determined by `bounds-of-thing-at-point'."
c851323f
RS
151 (let ((bounds (bounds-of-thing-at-point thing)))
152 (or bounds (error "No %s here" thing))
1a2b6c52
RS
153 (goto-char (car bounds))))
154
c851323f 155(defun end-of-thing (thing)
f5bd0689
CY
156 "Move point to the end of THING.
157The bounds of THING are determined by `bounds-of-thing-at-point'."
c851323f
RS
158 (let ((bounds (bounds-of-thing-at-point thing)))
159 (or bounds (error "No %s here" thing))
1a2b6c52
RS
160 (goto-char (cdr bounds))))
161
f1180544 162;; Special cases
1a2b6c52 163
f1180544 164;; Lines
9f5c7ace
RS
165
166;; bolp will be false when you click on the last line in the buffer
167;; and it has no final newline.
168
169(put 'line 'beginning-op
2a59b30d 170 (lambda () (if (bolp) (forward-line -1) (beginning-of-line))))
9f5c7ace 171
f1180544 172;; Sexps
1a2b6c52
RS
173
174(defun in-string-p ()
f5bd0689
CY
175 "Return non-nil if point is in a string.
176\[This is an internal function.]"
1a2b6c52
RS
177 (let ((orig (point)))
178 (save-excursion
179 (beginning-of-defun)
180 (nth 3 (parse-partial-sexp (point) orig)))))
181
182(defun end-of-sexp ()
f5bd0689
CY
183 "Move point to the end of the current sexp.
184\[This is an internal function.]"
00094c26 185 (let ((char-syntax (and (char-after) (char-syntax (char-after)))))
1a2b6c52
RS
186 (if (or (eq char-syntax ?\))
187 (and (eq char-syntax ?\") (in-string-p)))
188 (forward-char 1)
189 (forward-sexp 1))))
190
191(put 'sexp 'end-op 'end-of-sexp)
192
6f0e09d4 193(defun beginning-of-sexp ()
f5bd0689
CY
194 "Move point to the beginning of the current sexp.
195\[This is an internal function.]"
ce8fb8aa 196 (let ((char-syntax (char-syntax (char-before))))
6f0e09d4
RS
197 (if (or (eq char-syntax ?\()
198 (and (eq char-syntax ?\") (in-string-p)))
199 (forward-char -1)
200 (forward-sexp -1))))
201
202(put 'sexp 'beginning-op 'beginning-of-sexp)
203
f1180544 204;; Lists
1a2b6c52 205
965b9376
CY
206(put 'list 'bounds-of-thing-at-point 'thing-at-point-bounds-of-list-at-point)
207
208(defun thing-at-point-bounds-of-list-at-point ()
f5bd0689
CY
209 "Return the bounds of the list at point.
210\[Internal function used by `bounds-of-thing-at-point'.]"
965b9376
CY
211 (save-excursion
212 (let ((opoint (point))
213 (beg (condition-case nil
214 (progn (up-list -1)
215 (point))
216 (error nil))))
217 (condition-case nil
218 (if beg
219 (progn (forward-sexp)
220 (cons beg (point)))
221 ;; Are we are at the beginning of a top-level sexp?
222 (forward-sexp)
223 (let ((end (point)))
224 (backward-sexp)
225 (if (>= opoint (point))
226 (cons opoint end))))
227 (error nil)))))
1a2b6c52 228
e8974c48
DA
229;; Defuns
230
231(put 'defun 'beginning-op 'beginning-of-defun)
232(put 'defun 'end-op 'end-of-defun)
233(put 'defun 'forward-op 'end-of-defun)
234
4d61e7d5 235;; Filenames and URLs www.com/foo%32bar
1a2b6c52 236
839aacc9 237(defvar thing-at-point-file-name-chars "-~/[:alnum:]_.${}#%,:"
1a2b6c52
RS
238 "Characters allowable in filenames.")
239
f1180544 240(put 'filename 'end-op
839aacc9
DL
241 (lambda ()
242 (re-search-forward (concat "\\=[" thing-at-point-file-name-chars "]*")
243 nil t)))
1a2b6c52 244(put 'filename 'beginning-op
839aacc9
DL
245 (lambda ()
246 (if (re-search-backward (concat "[^" thing-at-point-file-name-chars "]")
247 nil t)
248 (forward-char)
249 (goto-char (point-min)))))
c851323f 250
d9cc804b 251(defvar thing-at-point-url-path-regexp
42986283 252 "[^]\t\n \"'<>[^`{}]*[^]\t\n \"'<>[^`{}.,;]+"
0408f074 253 "A regular expression probably matching the host and filename or e-mail part of a URL.")
d9cc804b
RS
254
255(defvar thing-at-point-short-url-regexp
d61bdd5d 256 (concat "[-A-Za-z0-9]+\\.[-A-Za-z0-9.]+" thing-at-point-url-path-regexp)
d9cc804b
RS
257 "A regular expression probably matching a URL without an access scheme.
258Hostname matching is stricter in this case than for
259``thing-at-point-url-regexp''.")
260
1c1766c7 261(defvar thing-at-point-uri-schemes
4f5d4668 262 ;; Officials from http://www.iana.org/assignments/uri-schemes.html
1c1766c7
MR
263 '("ftp://" "http://" "gopher://" "mailto:" "news:" "nntp:"
264 "telnet://" "wais://" "file:/" "prospero:" "z39.50s:" "z39.50r:"
265 "cid:" "mid:" "vemmi:" "service:" "imap:" "nfs:" "acap:" "rtsp:"
266 "tip:" "pop:" "data:" "dav:" "opaquelocktoken:" "sip:" "tel:" "fax:"
267 "modem:" "ldap:" "https://" "soap.beep:" "soap.beeps:" "urn:" "go:"
268 "afs:" "tn3270:" "mailserver:"
4f5d4668
RS
269 "crid:" "dict:" "dns:" "dtn:" "h323:" "im:" "info:" "ipp:"
270 "iris.beep:" "mtqp:" "mupdate:" "pres:" "sips:" "snmp:" "tag:"
271 "tftp:" "xmlrpc.beep:" "xmlrpc.beeps:" "xmpp:"
1c1766c7 272 ;; Compatibility
4f5d4668 273 "snews:" "irc:" "mms://" "mmsh://")
2a59b30d 274 "Uniform Resource Identifier (URI) Schemes.")
1c1766c7 275
d9cc804b 276(defvar thing-at-point-url-regexp
1c1766c7
MR
277 (concat "\\<\\(" (mapconcat 'identity thing-at-point-uri-schemes "\\|") "\\)"
278 thing-at-point-url-path-regexp)
d9cc804b
RS
279 "A regular expression probably matching a complete URL.")
280
281(defvar thing-at-point-markedup-url-regexp
282 "<URL:[^>]+>"
283 "A regular expression matching a URL marked up per RFC1738.
284This may contain whitespace (including newlines) .")
285
286(put 'url 'bounds-of-thing-at-point 'thing-at-point-bounds-of-url-at-point)
287(defun thing-at-point-bounds-of-url-at-point ()
2a59b30d
SM
288 (let ((strip (thing-at-point-looking-at
289 thing-at-point-markedup-url-regexp))) ;; (url "") short
290 (if (or strip
883d1f4b 291 (thing-at-point-looking-at thing-at-point-url-regexp)
d9cc804b 292 ;; Access scheme omitted?
2a59b30d
SM
293 ;; (setq short (thing-at-point-looking-at
294 ;; thing-at-point-short-url-regexp))
295 )
d9cc804b
RS
296 (let ((beginning (match-beginning 0))
297 (end (match-end 0)))
2a59b30d
SM
298 (when strip
299 (setq beginning (+ beginning 5))
300 (setq end (- end 1)))
d9cc804b
RS
301 (cons beginning end)))))
302
303(put 'url 'thing-at-point 'thing-at-point-url-at-point)
304(defun thing-at-point-url-at-point ()
305 "Return the URL around or before point.
340483df
DL
306
307Search backwards for the start of a URL ending at or after point. If
308no URL found, return nil. The access scheme will be prepended if
309absent: \"mailto:\" if the string contains \"@\", \"ftp://\" if it
310starts with \"ftp\" and not \"ftp:/\", or \"http://\" by default."
311
d9cc804b
RS
312 (let ((url "") short strip)
313 (if (or (setq strip (thing-at-point-looking-at
314 thing-at-point-markedup-url-regexp))
315 (thing-at-point-looking-at thing-at-point-url-regexp)
316 ;; Access scheme omitted?
317 (setq short (thing-at-point-looking-at
318 thing-at-point-short-url-regexp)))
319 (progn
320 (setq url (buffer-substring-no-properties (match-beginning 0)
321 (match-end 0)))
322 (and strip (setq url (substring url 5 -1))) ; Drop "<URL:" & ">"
323 ;; strip whitespace
84841dd1 324 (while (string-match "[ \t\n\r]+" url)
d9cc804b 325 (setq url (replace-match "" t t url)))
4f5d4668
RS
326 (and short (setq url (concat (cond ((string-match "^[a-zA-Z]+:" url)
327 ;; already has a URL scheme.
328 "")
329 ((string-match "@" url)
340483df
DL
330 "mailto:")
331 ;; e.g. ftp.swiss... or ftp-swiss...
332 ((string-match "^ftp" url)
333 "ftp://")
334 (t "http://"))
335 url)))
d9cc804b
RS
336 (if (string-equal "" url)
337 nil
338 url)))))
339
340;; The normal thingatpt mechanism doesn't work for complex regexps.
341;; This should work for almost any regexp wherever we are in the
342;; match. To do a perfect job for any arbitrary regexp would mean
343;; testing every position before point. Regexp searches won't find
344;; matches that straddle the start position so we search forwards once
345;; and then back repeatedly and then back up a char at a time.
346
347(defun thing-at-point-looking-at (regexp)
348 "Return non-nil if point is in or just after a match for REGEXP.
349Set the match data from the earliest such match ending at or after
350point."
351 (save-excursion
352 (let ((old-point (point)) match)
353 (and (looking-at regexp)
354 (>= (match-end 0) old-point)
355 (setq match (point)))
356 ;; Search back repeatedly from end of next match.
357 ;; This may fail if next match ends before this match does.
358 (re-search-forward regexp nil 'limit)
359 (while (and (re-search-backward regexp nil t)
360 (or (> (match-beginning 0) old-point)
361 (and (looking-at regexp) ; Extend match-end past search start
362 (>= (match-end 0) old-point)
363 (setq match (point))))))
364 (if (not match) nil
365 (goto-char match)
366 ;; Back up a char at a time in case search skipped
367 ;; intermediate match straddling search start pos.
368 (while (and (not (bobp))
369 (progn (backward-char 1) (looking-at regexp))
370 (>= (match-end 0) old-point)
371 (setq match (point))))
372 (goto-char match)
373 (looking-at regexp)))))
374
a1c9b4d0 375(put 'url 'end-op
2a59b30d
SM
376 (lambda ()
377 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
378 (if bounds
379 (goto-char (cdr bounds))
380 (error "No URL here")))))
c851323f 381(put 'url 'beginning-op
2a59b30d
SM
382 (lambda ()
383 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
384 (if bounds
385 (goto-char (car bounds))
386 (error "No URL here")))))
1a2b6c52 387
baef4cbe
KF
388;; Email addresses
389(defvar thing-at-point-email-regexp
86265518 390 "<?[-+_.~a-zA-Z][-+_.~:a-zA-Z0-9]*@[-.a-zA-Z0-9]+>?"
baef4cbe
KF
391 "A regular expression probably matching an email address.
392This does not match the real name portion, only the address, optionally
393with angle brackets.")
394
395;; Haven't set 'forward-op on 'email nor defined 'forward-email' because
396;; not sure they're actually needed, and URL seems to skip them too.
397;; Note that (end-of-thing 'email) and (beginning-of-thing 'email)
398;; work automagically, though.
399
400(put 'email 'bounds-of-thing-at-point
401 (lambda ()
402 (let ((thing (thing-at-point-looking-at thing-at-point-email-regexp)))
403 (if thing
404 (let ((beginning (match-beginning 0))
405 (end (match-end 0)))
406 (cons beginning end))))))
407
408(put 'email 'thing-at-point
409 (lambda ()
410 (let ((boundary-pair (bounds-of-thing-at-point 'email)))
411 (if boundary-pair
412 (buffer-substring-no-properties
413 (car boundary-pair) (cdr boundary-pair))))))
414
f1180544 415;; Whitespace
1a2b6c52 416
c851323f 417(defun forward-whitespace (arg)
f5bd0689
CY
418 "Move point to the end of the next sequence of whitespace chars.
419Each such sequence may be a single newline, or a sequence of
420consecutive space and/or tab characters.
421With prefix argument ARG, do it ARG times if positive, or move
422backwards ARG times if negative."
1a2b6c52 423 (interactive "p")
f1180544 424 (if (natnump arg)
9e594a2e 425 (re-search-forward "[ \t]+\\|\n" nil 'move arg)
c851323f 426 (while (< arg 0)
9e594a2e 427 (if (re-search-backward "[ \t]+\\|\n" nil 'move)
ec8bd86f 428 (or (eq (char-after (match-beginning 0)) ?\n)
1a2b6c52 429 (skip-chars-backward " \t")))
c851323f 430 (setq arg (1+ arg)))))
1a2b6c52 431
f1180544 432;; Buffer
1a2b6c52 433
206eef6c
SM
434(put 'buffer 'end-op (lambda () (goto-char (point-max))))
435(put 'buffer 'beginning-op (lambda () (goto-char (point-min))))
1a2b6c52 436
f1180544 437;; Symbols
1a2b6c52 438
c851323f 439(defun forward-symbol (arg)
f5bd0689
CY
440 "Move point to the next position that is the end of a symbol.
441A symbol is any sequence of characters that are in either the
442word constituent or symbol constituent syntax class.
443With prefix argument ARG, do it ARG times if positive, or move
444backwards ARG times if negative."
1a2b6c52 445 (interactive "p")
f1180544 446 (if (natnump arg)
9e594a2e 447 (re-search-forward "\\(\\sw\\|\\s_\\)+" nil 'move arg)
c851323f 448 (while (< arg 0)
9e594a2e 449 (if (re-search-backward "\\(\\sw\\|\\s_\\)+" nil 'move)
1a2b6c52 450 (skip-syntax-backward "w_"))
c851323f 451 (setq arg (1+ arg)))))
1a2b6c52 452
f1180544 453;; Syntax blocks
fde7326e
RS
454
455(defun forward-same-syntax (&optional arg)
f5bd0689
CY
456 "Move point past all characters with the same syntax class.
457With prefix argument ARG, do it ARG times if positive, or move
458backwards ARG times if negative."
fde7326e 459 (interactive "p")
34a008d9 460 (or arg (setq arg 1))
fde7326e 461 (while (< arg 0)
f1180544 462 (skip-syntax-backward
ce8fb8aa 463 (char-to-string (char-syntax (char-before))))
fde7326e
RS
464 (setq arg (1+ arg)))
465 (while (> arg 0)
ce8fb8aa 466 (skip-syntax-forward (char-to-string (char-syntax (char-after))))
fde7326e
RS
467 (setq arg (1- arg))))
468
f1180544 469;; Aliases
1a2b6c52 470
f5bd0689
CY
471(defun word-at-point ()
472 "Return the word at point. See `thing-at-point'."
473 (thing-at-point 'word))
474
475(defun sentence-at-point ()
476 "Return the sentence at point. See `thing-at-point'."
477 (thing-at-point 'sentence))
1a2b6c52 478
c851323f 479(defun read-from-whole-string (str)
2a59b30d 480 "Read a Lisp expression from STR.
c851323f
RS
481Signal an error if the entire string was not used."
482 (let* ((read-data (read-from-string str))
f1180544 483 (more-left
1a2b6c52 484 (condition-case nil
1b0d40de 485 ;; The call to `ignore' suppresses a compiler warning.
7f1422bc 486 (progn (ignore (read-from-string (substring str (cdr read-data))))
1a2b6c52
RS
487 t)
488 (end-of-file nil))))
489 (if more-left
490 (error "Can't read whole string")
491 (car read-data))))
492
f1180544
JB
493(defun form-at-point (&optional thing pred)
494 (let ((sexp (condition-case nil
c851323f 495 (read-from-whole-string (thing-at-point (or thing 'sexp)))
1a2b6c52 496 (error nil))))
c851323f 497 (if (or (not pred) (funcall pred sexp)) sexp)))
1a2b6c52 498
be64abcf 499;;;###autoload
de285f27
CY
500(defun sexp-at-point ()
501 "Return the sexp at point, or nil if none is found."
502 (form-at-point 'sexp))
be64abcf 503;;;###autoload
2a59b30d 504(defun symbol-at-point ()
de285f27 505 "Return the symbol at point, or nil if none is found."
2a59b30d
SM
506 (let ((thing (thing-at-point 'symbol)))
507 (if thing (intern thing))))
be64abcf 508;;;###autoload
de285f27
CY
509(defun number-at-point ()
510 "Return the number at point, or nil if none is found."
511 (form-at-point 'sexp 'numberp))
748b0d84 512(put 'number 'thing-at-point 'number-at-point)
be64abcf 513;;;###autoload
de285f27
CY
514(defun list-at-point ()
515 "Return the Lisp list at point, or nil if none is found."
516 (form-at-point 'list 'listp))
1a2b6c52 517
55535639 518;;; thingatpt.el ends here