38412
|
1 ;;; thingatpt.el --- get the `thing' at point
|
4934
|
2
|
29516
|
3 ;; Copyright (C) 1991,92,93,94,95,96,97,1998,2000
|
|
4 ;; 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
|
16629
|
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).
|
|
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)
|
4934
|
56 "Move forward to the end of the 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.
|
|
83 (let ((end (progn
|
17790
|
84 (funcall
|
|
85 (or (get thing 'end-op)
|
|
86 (function (lambda () (forward-thing thing 1)))))
|
18610
|
87 (point)))
|
|
88 (beg (progn
|
|
89 (funcall
|
|
90 (or (get thing 'beginning-op)
|
|
91 (function (lambda () (forward-thing thing -1)))))
|
17790
|
92 (point))))
|
18610
|
93 (if (not (and beg (> beg orig)))
|
|
94 ;; If that brings us all the way back to ORIG,
|
|
95 ;; it worked. But END may not be the real end.
|
|
96 ;; So find the real end that corresponds to BEG.
|
|
97 (let ((real-end
|
|
98 (progn
|
16668
|
99 (funcall
|
|
100 (or (get thing 'end-op)
|
|
101 (function (lambda () (forward-thing thing 1)))))
|
18610
|
102 (point))))
|
|
103 (if (and beg real-end (<= beg orig) (<= orig real-end))
|
|
104 (cons beg real-end)))
|
|
105 (goto-char orig)
|
|
106 ;; Try a second time, moving backward first and then forward,
|
|
107 ;; so that we can find a thing that ends at ORIG.
|
|
108 (let ((beg (progn
|
|
109 (funcall
|
|
110 (or (get thing 'beginning-op)
|
|
111 (function (lambda () (forward-thing thing -1)))))
|
|
112 (point)))
|
|
113 (end (progn
|
|
114 (funcall
|
|
115 (or (get thing 'end-op)
|
|
116 (function (lambda () (forward-thing thing 1)))))
|
|
117 (point)))
|
|
118 (real-beg
|
|
119 (progn
|
|
120 (funcall
|
|
121 (or (get thing 'beginning-op)
|
|
122 (function (lambda () (forward-thing thing -1)))))
|
|
123 (point))))
|
|
124 (if (and real-beg end (<= real-beg orig) (<= orig end))
|
|
125 (cons real-beg end))))))
|
|
126 (error nil)))))
|
4934
|
127
|
|
128 ;;;###autoload
|
16629
|
129 (defun thing-at-point (thing)
|
|
130 "Return the THING at point.
|
|
131 THING is a symbol which specifies the kind of syntactic entity you want.
|
|
132 Possibilities include `symbol', `list', `sexp', `defun', `filename', `url',
|
|
133 `word', `sentence', `whitespace', `line', `page' and others.
|
|
134
|
|
135 See the file `thingatpt.el' for documentation on how to define
|
|
136 a symbol as a valid THING."
|
18610
|
137 (if (get thing 'thing-at-point)
|
|
138 (funcall (get thing 'thing-at-point))
|
|
139 (let ((bounds (bounds-of-thing-at-point thing)))
|
|
140 (if bounds
|
|
141 (buffer-substring (car bounds) (cdr bounds))))))
|
4934
|
142
|
14169
|
143 ;; Go to beginning/end
|
4934
|
144
|
16629
|
145 (defun beginning-of-thing (thing)
|
|
146 (let ((bounds (bounds-of-thing-at-point thing)))
|
|
147 (or bounds (error "No %s here" thing))
|
4934
|
148 (goto-char (car bounds))))
|
|
149
|
16629
|
150 (defun end-of-thing (thing)
|
|
151 (let ((bounds (bounds-of-thing-at-point thing)))
|
|
152 (or bounds (error "No %s here" thing))
|
4934
|
153 (goto-char (cdr bounds))))
|
|
154
|
14169
|
155 ;; Special cases
|
4934
|
156
|
14169
|
157 ;; Lines
|
9931
|
158
|
|
159 ;; bolp will be false when you click on the last line in the buffer
|
|
160 ;; and it has no final newline.
|
|
161
|
|
162 (put 'line 'beginning-op
|
|
163 (function (lambda () (if (bolp) (forward-line -1) (beginning-of-line)))))
|
|
164
|
14169
|
165 ;; Sexps
|
4934
|
166
|
|
167 (defun in-string-p ()
|
|
168 (let ((orig (point)))
|
|
169 (save-excursion
|
|
170 (beginning-of-defun)
|
|
171 (nth 3 (parse-partial-sexp (point) orig)))))
|
|
172
|
|
173 (defun end-of-sexp ()
|
|
174 (let ((char-syntax (char-syntax (char-after (point)))))
|
|
175 (if (or (eq char-syntax ?\))
|
|
176 (and (eq char-syntax ?\") (in-string-p)))
|
|
177 (forward-char 1)
|
|
178 (forward-sexp 1))))
|
|
179
|
|
180 (put 'sexp 'end-op 'end-of-sexp)
|
|
181
|
18432
|
182 (defun beginning-of-sexp ()
|
|
183 (let ((char-syntax (char-syntax (char-before (point)))))
|
|
184 (if (or (eq char-syntax ?\()
|
|
185 (and (eq char-syntax ?\") (in-string-p)))
|
|
186 (forward-char -1)
|
|
187 (forward-sexp -1))))
|
|
188
|
|
189 (put 'sexp 'beginning-op 'beginning-of-sexp)
|
|
190
|
14169
|
191 ;; Lists
|
4934
|
192
|
|
193 (put 'list 'end-op (function (lambda () (up-list 1))))
|
|
194 (put 'list 'beginning-op 'backward-sexp)
|
|
195
|
16629
|
196 ;; Filenames and URLs
|
4934
|
197
|
16629
|
198 (defvar thing-at-point-file-name-chars "~/A-Za-z0-9---_.${}#%,:"
|
4934
|
199 "Characters allowable in filenames.")
|
|
200
|
|
201 (put 'filename 'end-op
|
29592
|
202 (lambda () (skip-chars-forward thing-at-point-file-name-chars)))
|
4934
|
203 (put 'filename 'beginning-op
|
29592
|
204 (lambda () (skip-chars-backward thing-at-point-file-name-chars)))
|
16629
|
205
|
18610
|
206 (defvar thing-at-point-url-path-regexp
|
|
207 "[^]\t\n \"'()<>[^`{}]*[^]\t\n \"'()<>[^`{}.,;]+"
|
|
208 "A regular expression probably matching the host, path or e-mail part of a URL.")
|
|
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
|
|
216 (defvar thing-at-point-url-regexp
|
|
217 (concat
|
30818
|
218 "\\<\\(https?://\\|ftp://\\|gopher://\\|telnet://\\|wais://\\|file:/\\|s?news:\\|mailto:\\)"
|
18610
|
219 thing-at-point-url-path-regexp)
|
|
220 "A regular expression probably matching a complete URL.")
|
|
221
|
|
222 (defvar thing-at-point-markedup-url-regexp
|
|
223 "<URL:[^>]+>"
|
|
224 "A regular expression matching a URL marked up per RFC1738.
|
|
225 This may contain whitespace (including newlines) .")
|
|
226
|
|
227 (put 'url 'bounds-of-thing-at-point 'thing-at-point-bounds-of-url-at-point)
|
|
228 (defun thing-at-point-bounds-of-url-at-point ()
|
|
229 (let ((url "") short strip)
|
|
230 (if (or (setq strip (thing-at-point-looking-at
|
|
231 thing-at-point-markedup-url-regexp))
|
|
232 (thing-at-point-looking-at thing-at-point-url-regexp)
|
|
233 ;; Access scheme omitted?
|
|
234 (setq short (thing-at-point-looking-at
|
|
235 thing-at-point-short-url-regexp)))
|
|
236 (let ((beginning (match-beginning 0))
|
|
237 (end (match-end 0)))
|
|
238 (cond (strip
|
|
239 (setq beginning (+ beginning 5))
|
|
240 (setq end (- end 1))))
|
|
241 (cons beginning end)))))
|
16629
|
242
|
18610
|
243 (put 'url 'thing-at-point 'thing-at-point-url-at-point)
|
|
244 (defun thing-at-point-url-at-point ()
|
|
245 "Return the URL around or before point.
|
20982
|
246
|
|
247 Search backwards for the start of a URL ending at or after point. If
|
|
248 no URL found, return nil. The access scheme will be prepended if
|
|
249 absent: \"mailto:\" if the string contains \"@\", \"ftp://\" if it
|
|
250 starts with \"ftp\" and not \"ftp:/\", or \"http://\" by default."
|
|
251
|
18610
|
252 (let ((url "") short strip)
|
|
253 (if (or (setq strip (thing-at-point-looking-at
|
|
254 thing-at-point-markedup-url-regexp))
|
|
255 (thing-at-point-looking-at thing-at-point-url-regexp)
|
|
256 ;; Access scheme omitted?
|
|
257 (setq short (thing-at-point-looking-at
|
|
258 thing-at-point-short-url-regexp)))
|
|
259 (progn
|
|
260 (setq url (buffer-substring-no-properties (match-beginning 0)
|
|
261 (match-end 0)))
|
|
262 (and strip (setq url (substring url 5 -1))) ; Drop "<URL:" & ">"
|
|
263 ;; strip whitespace
|
23764
|
264 (while (string-match "[ \t\n\r]+" url)
|
18610
|
265 (setq url (replace-match "" t t url)))
|
20982
|
266 (and short (setq url (concat (cond ((string-match "@" url)
|
|
267 "mailto:")
|
|
268 ;; e.g. ftp.swiss... or ftp-swiss...
|
|
269 ((string-match "^ftp" url)
|
|
270 "ftp://")
|
|
271 (t "http://"))
|
|
272 url)))
|
18610
|
273 (if (string-equal "" url)
|
|
274 nil
|
|
275 url)))))
|
|
276
|
|
277 ;; The normal thingatpt mechanism doesn't work for complex regexps.
|
|
278 ;; This should work for almost any regexp wherever we are in the
|
|
279 ;; match. To do a perfect job for any arbitrary regexp would mean
|
|
280 ;; testing every position before point. Regexp searches won't find
|
|
281 ;; matches that straddle the start position so we search forwards once
|
|
282 ;; and then back repeatedly and then back up a char at a time.
|
|
283
|
|
284 (defun thing-at-point-looking-at (regexp)
|
|
285 "Return non-nil if point is in or just after a match for REGEXP.
|
|
286 Set the match data from the earliest such match ending at or after
|
|
287 point."
|
|
288 (save-excursion
|
|
289 (let ((old-point (point)) match)
|
|
290 (and (looking-at regexp)
|
|
291 (>= (match-end 0) old-point)
|
|
292 (setq match (point)))
|
|
293 ;; Search back repeatedly from end of next match.
|
|
294 ;; This may fail if next match ends before this match does.
|
|
295 (re-search-forward regexp nil 'limit)
|
|
296 (while (and (re-search-backward regexp nil t)
|
|
297 (or (> (match-beginning 0) old-point)
|
|
298 (and (looking-at regexp) ; Extend match-end past search start
|
|
299 (>= (match-end 0) old-point)
|
|
300 (setq match (point))))))
|
|
301 (if (not match) nil
|
|
302 (goto-char match)
|
|
303 ;; Back up a char at a time in case search skipped
|
|
304 ;; intermediate match straddling search start pos.
|
|
305 (while (and (not (bobp))
|
|
306 (progn (backward-char 1) (looking-at regexp))
|
|
307 (>= (match-end 0) old-point)
|
|
308 (setq match (point))))
|
|
309 (goto-char match)
|
|
310 (looking-at regexp)))))
|
|
311
|
18682
|
312 (put 'url 'end-op
|
|
313 (function (lambda ()
|
|
314 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
|
315 (if bounds
|
|
316 (goto-char (cdr bounds))
|
|
317 (error "No URL here"))))))
|
16629
|
318 (put 'url 'beginning-op
|
18682
|
319 (function (lambda ()
|
|
320 (let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
|
321 (if bounds
|
|
322 (goto-char (car bounds))
|
|
323 (error "No URL here"))))))
|
4934
|
324
|
14169
|
325 ;; Whitespace
|
4934
|
326
|
16629
|
327 (defun forward-whitespace (arg)
|
4934
|
328 (interactive "p")
|
16629
|
329 (if (natnump arg)
|
17790
|
330 (re-search-forward "[ \t]+\\|\n" nil 'move arg)
|
16629
|
331 (while (< arg 0)
|
17790
|
332 (if (re-search-backward "[ \t]+\\|\n" nil 'move)
|
4934
|
333 (or (eq (char-after (match-beginning 0)) 10)
|
|
334 (skip-chars-backward " \t")))
|
16629
|
335 (setq arg (1+ arg)))))
|
4934
|
336
|
14169
|
337 ;; Buffer
|
4934
|
338
|
29592
|
339 (put 'buffer 'end-op (lambda () (goto-char (point-max))))
|
|
340 (put 'buffer 'beginning-op (lambda () (goto-char (point-min))))
|
4934
|
341
|
14169
|
342 ;; Symbols
|
4934
|
343
|
16629
|
344 (defun forward-symbol (arg)
|
4934
|
345 (interactive "p")
|
16629
|
346 (if (natnump arg)
|
17790
|
347 (re-search-forward "\\(\\sw\\|\\s_\\)+" nil 'move arg)
|
16629
|
348 (while (< arg 0)
|
17790
|
349 (if (re-search-backward "\\(\\sw\\|\\s_\\)+" nil 'move)
|
4934
|
350 (skip-syntax-backward "w_"))
|
16629
|
351 (setq arg (1+ arg)))))
|
4934
|
352
|
14169
|
353 ;; Syntax blocks
|
12593
|
354
|
|
355 (defun forward-same-syntax (&optional arg)
|
|
356 (interactive "p")
|
|
357 (while (< arg 0)
|
|
358 (skip-syntax-backward
|
|
359 (char-to-string (char-syntax (char-after (1- (point))))))
|
|
360 (setq arg (1+ arg)))
|
|
361 (while (> arg 0)
|
|
362 (skip-syntax-forward (char-to-string (char-syntax (char-after (point)))))
|
|
363 (setq arg (1- arg))))
|
|
364
|
14169
|
365 ;; Aliases
|
4934
|
366
|
|
367 (defun word-at-point () (thing-at-point 'word))
|
|
368 (defun sentence-at-point () (thing-at-point 'sentence))
|
|
369
|
16629
|
370 (defun read-from-whole-string (str)
|
|
371 "Read a lisp expression from STR.
|
|
372 Signal an error if the entire string was not used."
|
|
373 (let* ((read-data (read-from-string str))
|
4934
|
374 (more-left
|
|
375 (condition-case nil
|
16629
|
376 (progn (read-from-string (substring str (cdr read-data)))
|
4934
|
377 t)
|
|
378 (end-of-file nil))))
|
|
379 (if more-left
|
|
380 (error "Can't read whole string")
|
|
381 (car read-data))))
|
|
382
|
16629
|
383 (defun form-at-point (&optional thing pred)
|
4934
|
384 (let ((sexp (condition-case nil
|
16629
|
385 (read-from-whole-string (thing-at-point (or thing 'sexp)))
|
4934
|
386 (error nil))))
|
16629
|
387 (if (or (not pred) (funcall pred sexp)) sexp)))
|
4934
|
388
|
27581
|
389 ;;;###autoload
|
4934
|
390 (defun sexp-at-point () (form-at-point 'sexp))
|
27581
|
391 ;;;###autoload
|
4934
|
392 (defun symbol-at-point () (form-at-point 'sexp 'symbolp))
|
27581
|
393 ;;;###autoload
|
4934
|
394 (defun number-at-point () (form-at-point 'sexp 'numberp))
|
27581
|
395 ;;;###autoload
|
4934
|
396 (defun list-at-point () (form-at-point 'list 'listp))
|
|
397
|
38412
|
398 ;;; thingatpt.el ends here
|