ad19758b4a61fc1d66866f72ff2eb7d548dcc1bf
[bpt/emacs.git] / lisp / net / eww.el
1 ;;; eww.el --- Emacs Web Wowser
2
3 ;; Copyright (C) 2013 Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: html
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 (eval-when-compile (require 'cl))
28 (require 'format-spec)
29 (require 'shr)
30 (require 'url)
31 (require 'mm-url)
32
33 (defgroup eww nil
34 "Emacs Web Wowser"
35 :version "24.4"
36 :group 'hypermedia
37 :prefix "eww-")
38
39 (defcustom eww-header-line-format "%t: %u"
40 "Header line format.
41 - %t is replaced by the title.
42 - %u is replaced by the URL."
43 :group 'eww
44 :type 'string)
45
46 (defface eww-form-submit
47 '((((type x w32 ns) (class color)) ; Like default mode line
48 :box (:line-width 2 :style released-button)
49 :background "#808080" :foreground "black"))
50 "Face for eww buffer buttons."
51 :version "24.4"
52 :group 'eww)
53
54 (defface eww-form-checkbox
55 '((((type x w32 ns) (class color)) ; Like default mode line
56 :box (:line-width 2 :style released-button)
57 :background "lightgrey" :foreground "black"))
58 "Face for eww buffer buttons."
59 :version "24.4"
60 :group 'eww)
61
62 (defface eww-form-select
63 '((((type x w32 ns) (class color)) ; Like default mode line
64 :box (:line-width 2 :style released-button)
65 :background "lightgrey" :foreground "black"))
66 "Face for eww buffer buttons."
67 :version "24.4"
68 :group 'eww)
69
70 (defface eww-form-text
71 '((t (:background "#505050"
72 :foreground "white"
73 :box (:line-width 1))))
74 "Face for eww text inputs."
75 :version "24.4"
76 :group 'eww)
77
78 (defvar eww-current-url nil)
79 (defvar eww-current-title ""
80 "Title of current page.")
81 (defvar eww-history nil)
82
83 (defvar eww-next-url nil)
84 (defvar eww-previous-url nil)
85 (defvar eww-up-url nil)
86 (defvar eww-home-url nil)
87 (defvar eww-start-url nil)
88 (defvar eww-contents-url nil)
89
90 ;;;###autoload
91 (defun eww (url)
92 "Fetch URL and render the page."
93 (interactive "sUrl: ")
94 (unless (string-match-p "\\`[a-zA-Z][-a-zA-Z0-9+.]*://" url)
95 (setq url (concat "http://" url)))
96 (url-retrieve url 'eww-render (list url)))
97
98 ;;;###autoload
99 (defun eww-open-file (file)
100 "Render a file using EWW."
101 (interactive "fFile: ")
102 (eww (concat "file://" (expand-file-name file))))
103
104 (defun eww-render (status url &optional point)
105 (let ((redirect (plist-get status :redirect)))
106 (when redirect
107 (setq url redirect)))
108 (set (make-local-variable 'eww-next-url) nil)
109 (set (make-local-variable 'eww-previous-url) nil)
110 (set (make-local-variable 'eww-up-url) nil)
111 (set (make-local-variable 'eww-home-url) nil)
112 (set (make-local-variable 'eww-start-url) nil)
113 (set (make-local-variable 'eww-contents-url) nil)
114 (let* ((headers (eww-parse-headers))
115 (shr-target-id
116 (and (string-match "#\\(.*\\)" url)
117 (match-string 1 url)))
118 (content-type
119 (mail-header-parse-content-type
120 (or (cdr (assoc "content-type" headers))
121 "text/plain")))
122 (charset (intern
123 (downcase
124 (or (cdr (assq 'charset (cdr content-type)))
125 (eww-detect-charset (equal (car content-type)
126 "text/html"))
127 "utf8"))))
128 (data-buffer (current-buffer)))
129 (unwind-protect
130 (progn
131 (cond
132 ((equal (car content-type) "text/html")
133 (eww-display-html charset url))
134 ((string-match "^image/" (car content-type))
135 (eww-display-image))
136 (t
137 (eww-display-raw charset)))
138 (cond
139 (point
140 (goto-char point))
141 (shr-target-id
142 (let ((point (next-single-property-change
143 (point-min) 'shr-target-id)))
144 (when point
145 (goto-char (1+ point)))))))
146 (kill-buffer data-buffer))))
147
148 (defun eww-parse-headers ()
149 (let ((headers nil))
150 (goto-char (point-min))
151 (while (and (not (eobp))
152 (not (eolp)))
153 (when (looking-at "\\([^:]+\\): *\\(.*\\)")
154 (push (cons (downcase (match-string 1))
155 (match-string 2))
156 headers))
157 (forward-line 1))
158 (unless (eobp)
159 (forward-line 1))
160 headers))
161
162 (defun eww-detect-charset (html-p)
163 (let ((case-fold-search t)
164 (pt (point)))
165 (or (and html-p
166 (re-search-forward
167 "<meta[\t\n\r ]+[^>]*charset=\"?\\([^\t\n\r \"/>]+\\)" nil t)
168 (goto-char pt)
169 (match-string 1))
170 (and (looking-at
171 "[\t\n\r ]*<\\?xml[\t\n\r ]+[^>]*encoding=\"\\([^\"]+\\)")
172 (match-string 1)))))
173
174 (defun eww-display-html (charset url)
175 (unless (eq charset 'utf8)
176 (decode-coding-region (point) (point-max) charset))
177 (let ((document
178 (list
179 'base (list (cons 'href url))
180 (libxml-parse-html-region (point) (point-max)))))
181 (eww-setup-buffer)
182 (setq eww-current-url url)
183 (eww-update-header-line-format)
184 (let ((inhibit-read-only t)
185 (after-change-functions nil)
186 (shr-width nil)
187 (shr-external-rendering-functions
188 '((title . eww-tag-title)
189 (form . eww-tag-form)
190 (input . eww-tag-input)
191 (textarea . eww-tag-textarea)
192 (body . eww-tag-body)
193 (select . eww-tag-select)
194 (link . eww-tag-link)
195 (a . eww-tag-a))))
196 (shr-insert-document document))
197 (goto-char (point-min))))
198
199 (defun eww-handle-link (cont)
200 (let* ((rel (assq :rel cont))
201 (href (assq :href cont))
202 (where (assoc
203 ;; The text associated with :rel is case-insensitive.
204 (if rel (downcase (cdr rel)))
205 '(("next" . eww-next-url)
206 ;; Texinfo uses "previous", but HTML specifies
207 ;; "prev", so recognize both.
208 ("previous" . eww-previous-url)
209 ("prev" . eww-previous-url)
210 ;; HTML specifies "start" but also "contents",
211 ;; and Gtk seems to use "home". Recognize
212 ;; them all; but store them in different
213 ;; variables so that we can readily choose the
214 ;; "best" one.
215 ("start" . eww-start-url)
216 ("home" . eww-home-url)
217 ("contents" . eww-contents-url)
218 ("up" . eww-up-url)))))
219 (and href
220 where
221 (set (cdr where) (cdr href)))))
222
223 (defun eww-tag-link (cont)
224 (eww-handle-link cont)
225 (shr-generic cont))
226
227 (defun eww-tag-a (cont)
228 (eww-handle-link cont)
229 (shr-tag-a cont))
230
231 (defun eww-update-header-line-format ()
232 (if eww-header-line-format
233 (setq header-line-format
234 (replace-regexp-in-string
235 "%" "%%"
236 (format-spec eww-header-line-format
237 `((?u . ,eww-current-url)
238 (?t . ,eww-current-title)))))
239 (setq header-line-format nil)))
240
241 (defun eww-tag-title (cont)
242 (setq eww-current-title "")
243 (dolist (sub cont)
244 (when (eq (car sub) 'text)
245 (setq eww-current-title (concat eww-current-title (cdr sub)))))
246 (eww-update-header-line-format))
247
248 (defun eww-tag-body (cont)
249 (let* ((start (point))
250 (fgcolor (cdr (or (assq :fgcolor cont)
251 (assq :text cont))))
252 (bgcolor (cdr (assq :bgcolor cont)))
253 (shr-stylesheet (list (cons 'color fgcolor)
254 (cons 'background-color bgcolor))))
255 (shr-generic cont)
256 (eww-colorize-region start (point) fgcolor bgcolor)))
257
258 (defun eww-colorize-region (start end fg &optional bg)
259 (when (or fg bg)
260 (let ((new-colors (shr-color-check fg bg)))
261 (when new-colors
262 (when fg
263 (add-face-text-property start end
264 (list :foreground (cadr new-colors))
265 t))
266 (when bg
267 (add-face-text-property start end
268 (list :background (car new-colors))
269 t))))))
270
271 (defun eww-display-raw (charset)
272 (let ((data (buffer-substring (point) (point-max))))
273 (eww-setup-buffer)
274 (let ((inhibit-read-only t))
275 (insert data))
276 (goto-char (point-min))))
277
278 (defun eww-display-image ()
279 (let ((data (buffer-substring (point) (point-max))))
280 (eww-setup-buffer)
281 (let ((inhibit-read-only t))
282 (shr-put-image data nil))
283 (goto-char (point-min))))
284
285 (defun eww-setup-buffer ()
286 (pop-to-buffer (get-buffer-create "*eww*"))
287 (remove-overlays)
288 (let ((inhibit-read-only t))
289 (erase-buffer))
290 (eww-mode))
291
292 (defvar eww-mode-map
293 (let ((map (make-sparse-keymap)))
294 (suppress-keymap map)
295 (define-key map "q" 'eww-quit)
296 (define-key map "g" 'eww-reload)
297 (define-key map [tab] 'shr-next-link)
298 (define-key map [backtab] 'shr-previous-link)
299 (define-key map [delete] 'scroll-down-command)
300 (define-key map "\177" 'scroll-down-command)
301 (define-key map " " 'scroll-up-command)
302 (define-key map "l" 'eww-back-url)
303 (define-key map "n" 'eww-next-url)
304 (define-key map "p" 'eww-previous-url)
305 (define-key map "u" 'eww-up-url)
306 (define-key map "t" 'eww-top-url)
307 map))
308
309 (define-derived-mode eww-mode nil "eww"
310 "Mode for browsing the web.
311
312 \\{eww-mode-map}"
313 (set (make-local-variable 'eww-current-url) 'author)
314 (set (make-local-variable 'browse-url-browser-function) 'eww-browse-url)
315 (set (make-local-variable 'after-change-functions) 'eww-process-text-input)
316 ;;(setq buffer-read-only t)
317 )
318
319 (defun eww-browse-url (url &optional new-window)
320 (when (and (equal major-mode 'eww-mode)
321 eww-current-url)
322 (push (list eww-current-url (point))
323 eww-history))
324 (eww url))
325
326 (defun eww-quit ()
327 "Exit the Emacs Web Wowser."
328 (interactive)
329 (setq eww-history nil)
330 (kill-buffer (current-buffer)))
331
332 (defun eww-back-url ()
333 "Go to the previously displayed page."
334 (interactive)
335 (when (zerop (length eww-history))
336 (error "No previous page"))
337 (let ((prev (pop eww-history)))
338 (url-retrieve (car prev) 'eww-render (list (car prev) (cadr prev)))))
339
340 (defun eww-next-url ()
341 "Go to the page marked `next'.
342 A page is marked `next' if rel=\"next\" appears in a <link>
343 or <a> tag."
344 (interactive)
345 (if eww-next-url
346 (eww-browse-url (shr-expand-url eww-next-url eww-current-url))
347 (error "No `next' on this page")))
348
349 (defun eww-previous-url ()
350 "Go to the page marked `previous'.
351 A page is marked `previous' if rel=\"previous\" appears in a <link>
352 or <a> tag."
353 (interactive)
354 (if eww-previous-url
355 (eww-browse-url (shr-expand-url eww-previous-url eww-current-url))
356 (error "No `previous' on this page")))
357
358 (defun eww-up-url ()
359 "Go to the page marked `up'.
360 A page is marked `up' if rel=\"up\" appears in a <link>
361 or <a> tag."
362 (interactive)
363 (if eww-up-url
364 (eww-browse-url (shr-expand-url eww-up-url eww-current-url))
365 (error "No `up' on this page")))
366
367 (defun eww-top-url ()
368 "Go to the page marked `top'.
369 A page is marked `top' if rel=\"start\", rel=\"home\", or rel=\"contents\"
370 appears in a <link> or <a> tag."
371 (interactive)
372 (let ((best-url (or eww-start-url
373 eww-contents-url
374 eww-home-url)))
375 (if best-url
376 (eww-browse-url (shr-expand-url best-url eww-current-url))
377 (error "No `top' for this page"))))
378
379 (defun eww-reload ()
380 "Reload the current page."
381 (interactive)
382 (url-retrieve eww-current-url 'eww-render
383 (list eww-current-url (point))))
384
385 ;; Form support.
386
387 (defvar eww-form nil)
388
389 (defvar eww-submit-map
390 (let ((map (make-sparse-keymap)))
391 (define-key map "\r" 'eww-submit)
392 map))
393
394 (defvar eww-checkbox-map
395 (let ((map (make-sparse-keymap)))
396 (define-key map [space] 'eww-toggle-checkbox)
397 (define-key map "\r" 'eww-toggle-checkbox)
398 map))
399
400 (defvar eww-text-map
401 (let ((map (make-keymap)))
402 (set-keymap-parent map text-mode-map)
403 (define-key map "\r" 'eww-submit)
404 (define-key map [(control a)] 'eww-beginning-of-text)
405 (define-key map [(control e)] 'eww-end-of-text)
406 (define-key map [tab] 'shr-next-link)
407 (define-key map [backtab] 'shr-previous-link)
408 map))
409
410 (defvar eww-textarea-map
411 (let ((map (make-keymap)))
412 (set-keymap-parent map text-mode-map)
413 (define-key map "\r" 'forward-line)
414 (define-key map [tab] 'shr-next-link)
415 (define-key map [backtab] 'shr-previous-link)
416 map))
417
418 (defvar eww-select-map
419 (let ((map (make-sparse-keymap)))
420 (define-key map "\r" 'eww-change-select)
421 map))
422
423 (defun eww-beginning-of-text ()
424 "Move to the start of the input field."
425 (interactive)
426 (goto-char (eww-beginning-of-field)))
427
428 (defun eww-end-of-text ()
429 "Move to the end of the text in the input field."
430 (interactive)
431 (goto-char (eww-end-of-field))
432 (let ((start (eww-beginning-of-field)))
433 (while (and (equal (following-char) ? )
434 (> (point) start))
435 (forward-char -1))
436 (when (> (point) start)
437 (forward-char 1))))
438
439 (defun eww-beginning-of-field ()
440 (cond
441 ((bobp)
442 (point))
443 ((not (eq (get-text-property (point) 'eww-form)
444 (get-text-property (1- (point)) 'eww-form)))
445 (point))
446 (t
447 (previous-single-property-change
448 (point) 'eww-form nil (point-min)))))
449
450 (defun eww-end-of-field ()
451 (1- (next-single-property-change
452 (point) 'eww-form nil (point-max))))
453
454 (defun eww-tag-form (cont)
455 (let ((eww-form
456 (list (assq :method cont)
457 (assq :action cont)))
458 (start (point)))
459 (shr-ensure-paragraph)
460 (shr-generic cont)
461 (unless (bolp)
462 (insert "\n"))
463 (insert "\n")
464 (when (> (point) start)
465 (put-text-property start (1+ start)
466 'eww-form eww-form))))
467
468 (defun eww-form-submit (cont)
469 (let ((start (point))
470 (value (cdr (assq :value cont))))
471 (setq value
472 (if (zerop (length value))
473 "Submit"
474 value))
475 (insert value)
476 (add-face-text-property start (point) 'eww-form-submit)
477 (put-text-property start (point) 'eww-form
478 (list :eww-form eww-form
479 :value value
480 :type "submit"
481 :name (cdr (assq :name cont))))
482 (put-text-property start (point) 'keymap eww-submit-map)
483 (insert " ")))
484
485 (defun eww-form-checkbox (cont)
486 (let ((start (point)))
487 (if (cdr (assq :checked cont))
488 (insert "[X]")
489 (insert "[ ]"))
490 (add-face-text-property start (point) 'eww-form-checkbox)
491 (put-text-property start (point) 'eww-form
492 (list :eww-form eww-form
493 :value (cdr (assq :value cont))
494 :type (downcase (cdr (assq :type cont)))
495 :checked (cdr (assq :checked cont))
496 :name (cdr (assq :name cont))))
497 (put-text-property start (point) 'keymap eww-checkbox-map)
498 (insert " ")))
499
500 (defun eww-form-text (cont)
501 (let ((start (point))
502 (type (downcase (or (cdr (assq :type cont))
503 "text")))
504 (value (or (cdr (assq :value cont)) ""))
505 (width (string-to-number
506 (or (cdr (assq :size cont))
507 "40"))))
508 (insert value)
509 (when (< (length value) width)
510 (insert (make-string (- width (length value)) ? )))
511 (put-text-property start (point) 'face 'eww-form-text)
512 (put-text-property start (point) 'local-map eww-text-map)
513 (put-text-property start (point) 'inhibit-read-only t)
514 (put-text-property start (point) 'eww-form
515 (list :eww-form eww-form
516 :value value
517 :type type
518 :name (cdr (assq :name cont))))
519 (insert " ")))
520
521 (defun eww-process-text-input (beg end length)
522 (let* ((form (get-text-property end 'eww-form))
523 (properties (text-properties-at end))
524 (type (plist-get form :type)))
525 (when (and form
526 (member type '("text" "password" "textarea")))
527 (cond
528 ((zerop length)
529 ;; Delete some space at the end.
530 (save-excursion
531 (goto-char
532 (if (equal type "textarea")
533 (1- (line-end-position))
534 (eww-end-of-field)))
535 (let ((new (- end beg)))
536 (while (and (> new 0)
537 (eql (following-char) ? ))
538 (delete-region (point) (1+ (point)))
539 (setq new (1- new))))
540 (set-text-properties beg end properties)))
541 ((> length 0)
542 ;; Add padding.
543 (save-excursion
544 (goto-char
545 (if (equal type "textarea")
546 (1- (line-end-position))
547 (eww-end-of-field)))
548 (let ((start (point)))
549 (insert (make-string length ? ))
550 (set-text-properties start (point) properties)))))
551 (let ((value (buffer-substring-no-properties
552 (eww-beginning-of-field)
553 (eww-end-of-field))))
554 (when (string-match " +\\'" value)
555 (setq value (substring value 0 (match-beginning 0))))
556 (plist-put form :value value)
557 (when (equal type "password")
558 ;; Display passwords as asterisks.
559 (let ((start (eww-beginning-of-field)))
560 (put-text-property start (+ start (length value))
561 'display (make-string (length value) ?*))))))))
562
563 (defun eww-tag-textarea (cont)
564 (let ((start (point))
565 (value (or (cdr (assq :value cont)) ""))
566 (lines (string-to-number
567 (or (cdr (assq :rows cont))
568 "10")))
569 (width (string-to-number
570 (or (cdr (assq :cols cont))
571 "10")))
572 end)
573 (shr-ensure-newline)
574 (insert value)
575 (shr-ensure-newline)
576 (when (< (count-lines start (point)) lines)
577 (dotimes (i (- lines (count-lines start (point))))
578 (insert "\n")))
579 (setq end (point-marker))
580 (goto-char start)
581 (while (< (point) end)
582 (end-of-line)
583 (let ((pad (- width (- (point) (line-beginning-position)))))
584 (when (> pad 0)
585 (insert (make-string pad ? ))))
586 (add-face-text-property (line-beginning-position)
587 (point) 'eww-form-text)
588 (put-text-property (line-beginning-position) (point)
589 'local-map eww-textarea-map)
590 (forward-line 1))
591 (put-text-property start (point) 'eww-form
592 (list :eww-form eww-form
593 :value value
594 :type "textarea"
595 :name (cdr (assq :name cont))))))
596
597 (defun eww-tag-input (cont)
598 (let ((type (downcase (or (cdr (assq :type cont))
599 "text")))
600 (start (point)))
601 (cond
602 ((or (equal type "checkbox")
603 (equal type "radio"))
604 (eww-form-checkbox cont))
605 ((equal type "submit")
606 (eww-form-submit cont))
607 ((equal type "hidden")
608 (let ((form eww-form)
609 (name (cdr (assq :name cont))))
610 ;; Don't add <input type=hidden> elements repeatedly.
611 (while (and form
612 (or (not (consp (car form)))
613 (not (eq (caar form) 'hidden))
614 (not (equal (plist-get (cdr (car form)) :name)
615 name))))
616 (setq form (cdr form)))
617 (unless form
618 (nconc eww-form (list
619 (list 'hidden
620 :name name
621 :value (cdr (assq :value cont))))))))
622 (t
623 (eww-form-text cont)))
624 (unless (= start (point))
625 (put-text-property start (1+ start) 'help-echo "Input field"))))
626
627 (defun eww-tag-select (cont)
628 (shr-ensure-paragraph)
629 (let ((menu (list :name (cdr (assq :name cont))
630 :eww-form eww-form))
631 (options nil)
632 (start (point))
633 (max 0))
634 (dolist (elem cont)
635 (when (eq (car elem) 'option)
636 (when (cdr (assq :selected (cdr elem)))
637 (nconc menu (list :value
638 (cdr (assq :value (cdr elem))))))
639 (let ((display (or (cdr (assq 'text (cdr elem))) "")))
640 (setq max (max max (length display)))
641 (push (list 'item
642 :value (cdr (assq :value (cdr elem)))
643 :display display)
644 options))))
645 (when options
646 (setq options (nreverse options))
647 ;; If we have no selected values, default to the first value.
648 (unless (plist-get menu :value)
649 (nconc menu (list :value (nth 2 (car options)))))
650 (nconc menu options)
651 (let ((selected (eww-select-display menu)))
652 (insert selected
653 (make-string (- max (length selected)) ? )))
654 (put-text-property start (point) 'eww-form menu)
655 (add-face-text-property start (point) 'eww-form-select)
656 (put-text-property start (point) 'keymap eww-select-map)
657 (shr-ensure-paragraph))))
658
659 (defun eww-select-display (select)
660 (let ((value (plist-get select :value))
661 display)
662 (dolist (elem select)
663 (when (and (consp elem)
664 (eq (car elem) 'item)
665 (equal value (plist-get (cdr elem) :value)))
666 (setq display (plist-get (cdr elem) :display))))
667 display))
668
669 (defun eww-change-select ()
670 "Change the value of the select drop-down menu under point."
671 (interactive)
672 (let* ((input (get-text-property (point) 'eww-form))
673 (properties (text-properties-at (point)))
674 (completion-ignore-case t)
675 (options
676 (delq nil
677 (mapcar (lambda (elem)
678 (and (consp elem)
679 (eq (car elem) 'item)
680 (cons (plist-get (cdr elem) :display)
681 (plist-get (cdr elem) :value))))
682 input)))
683 (display
684 (completing-read "Change value: " options nil 'require-match))
685 (inhibit-read-only t))
686 (plist-put input :value (cdr (assoc-string display options t)))
687 (goto-char
688 (eww-update-field display))))
689
690 (defun eww-update-field (string)
691 (let ((properties (text-properties-at (point)))
692 (start (eww-beginning-of-field))
693 (end (1+ (eww-end-of-field))))
694 (delete-region start end)
695 (insert string
696 (make-string (- (- end start) (length string)) ? ))
697 (set-text-properties start end properties)
698 start))
699
700 (defun eww-toggle-checkbox ()
701 "Toggle the value of the checkbox under point."
702 (interactive)
703 (let* ((input (get-text-property (point) 'eww-form))
704 (type (plist-get input :type)))
705 (if (equal type "checkbox")
706 (goto-char
707 (1+
708 (if (plist-get input :checked)
709 (progn
710 (plist-put input :checked nil)
711 (eww-update-field "[ ]"))
712 (plist-put input :checked t)
713 (eww-update-field "[X]"))))
714 ;; Radio button. Switch all other buttons off.
715 (let ((name (plist-get input :name)))
716 (save-excursion
717 (dolist (elem (eww-inputs (plist-get input :eww-form)))
718 (when (equal (plist-get (cdr elem) :name) name)
719 (goto-char (car elem))
720 (if (not (eq (cdr elem) input))
721 (progn
722 (plist-put input :checked nil)
723 (eww-update-field "[ ]"))
724 (plist-put input :checked t)
725 (eww-update-field "[X]")))))
726 (forward-char 1)))))
727
728 (defun eww-inputs (form)
729 (let ((start (point-min))
730 (inputs nil))
731 (while (and start
732 (< start (point-max)))
733 (when (or (get-text-property start 'eww-form)
734 (setq start (next-single-property-change start 'eww-form)))
735 (when (eq (plist-get (get-text-property start 'eww-form) :eww-form)
736 form)
737 (push (cons start (get-text-property start 'eww-form))
738 inputs))
739 (setq start (next-single-property-change start 'eww-form))))
740 (nreverse inputs)))
741
742 (defun eww-input-value (input)
743 (let ((type (plist-get input :type))
744 (value (plist-get input :value)))
745 (cond
746 ((equal type "textarea")
747 (with-temp-buffer
748 (insert value)
749 (goto-char (point-min))
750 (while (re-search-forward "^ +\n\\| +$" nil t)
751 (replace-match "" t t))
752 (buffer-string)))
753 (t
754 (if (string-match " +\\'" value)
755 (substring value 0 (match-beginning 0))
756 value)))))
757
758 (defun eww-submit ()
759 "Submit the current form."
760 (interactive)
761 (let* ((this-input (get-text-property (point) 'eww-form))
762 (form (plist-get this-input :eww-form))
763 values next-submit)
764 (dolist (elem (sort (eww-inputs form)
765 (lambda (o1 o2)
766 (< (car o1) (car o2)))))
767 (let* ((input (cdr elem))
768 (input-start (car elem))
769 (name (plist-get input :name)))
770 (when name
771 (cond
772 ((member (plist-get input :type) '("checkbox" "radio"))
773 (when (plist-get input :checked)
774 (push (cons name (plist-get input :value))
775 values)))
776 ((equal (plist-get input :type) "submit")
777 ;; We want the values from buttons if we hit a button if
778 ;; we hit enter on it, or if it's the first button after
779 ;; the field we did hit return on.
780 (when (or (eq input this-input)
781 (and (not (eq input this-input))
782 (null next-submit)
783 (> input-start (point))))
784 (setq next-submit t)
785 (push (cons name (plist-get input :value))
786 values)))
787 (t
788 (push (cons name (eww-input-value input))
789 values))))))
790 (dolist (elem form)
791 (when (and (consp elem)
792 (eq (car elem) 'hidden))
793 (push (cons (plist-get (cdr elem) :name)
794 (plist-get (cdr elem) :value))
795 values)))
796 (if (and (stringp (cdr (assq :method form)))
797 (equal (downcase (cdr (assq :method form))) "post"))
798 (let ((url-request-method "POST")
799 (url-request-extra-headers
800 '(("Content-Type" . "application/x-www-form-urlencoded")))
801 (url-request-data (mm-url-encode-www-form-urlencoded values)))
802 (eww-browse-url (shr-expand-url (cdr (assq :action form))
803 eww-current-url)))
804 (eww-browse-url
805 (concat
806 (if (cdr (assq :action form))
807 (shr-expand-url (cdr (assq :action form))
808 eww-current-url)
809 eww-current-url)
810 "?"
811 (mm-url-encode-www-form-urlencoded values))))))
812
813 (provide 'eww)
814
815 ;;; eww.el ends here