Make form submission work
[gnus] / lisp / 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 'shr)
29 (require 'url)
30 (require 'mm-url)
31
32 (defvar eww-current-url nil)
33 (defvar eww-history nil)
34
35 (defun eww (url)
36   "Fetch URL and render the page."
37   (interactive "sUrl: ")
38   (url-retrieve url 'eww-render (list url)))
39
40 (defun eww-render (status url &optional point)
41   (let* ((headers (eww-parse-headers))
42          (content-type
43           (mail-header-parse-content-type
44            (or (cdr (assoc "content-type" headers))
45                "text/plain")))
46          (charset (intern
47                    (downcase
48                     (or (cdr (assq 'charset (cdr content-type)))
49                         "utf8"))))
50          (data-buffer (current-buffer)))
51     (unwind-protect
52         (progn
53           (cond
54            ((equal (car content-type) "text/html")
55             (eww-display-html charset url))
56            ((string-match "^image/" (car content-type))
57             (eww-display-image))
58            (t
59             (eww-display-raw charset)))
60           (when point
61             (goto-char point)))
62       (kill-buffer data-buffer))))
63
64 (defun eww-parse-headers ()
65   (let ((headers nil))
66     (while (and (not (eobp))
67                 (not (eolp)))
68       (when (looking-at "\\([^:]+\\): *\\(.*\\)")
69         (push (cons (downcase (match-string 1))
70                     (match-string 2))
71               headers))
72       (forward-line 1))
73     (unless (eobp)
74       (forward-line 1))
75     headers))
76
77 (defun eww-display-html (charset url)
78   (unless (eq charset 'utf8)
79     (decode-coding-region (point) (point-max) charset))
80   (let ((document
81          (list
82           'base (list (cons 'href url))
83           (libxml-parse-html-region (point) (point-max)))))
84     (eww-setup-buffer)
85     (setq eww-current-url url)
86     (let ((inhibit-read-only t)
87           (shr-external-rendering-functions
88            '((form . eww-tag-form)
89              (input . eww-tag-input)
90              (submit . eww-tag-submit))))
91       (shr-insert-document document)
92       (eww-convert-widgets))
93     (goto-char (point-min))))
94
95 (defun eww-display-raw (charset)
96   (let ((data (buffer-substring (point) (point-max))))
97     (eww-setup-buffer)
98     (let ((inhibit-read-only t))
99       (insert data))
100     (goto-char (point-min))))
101
102 (defun eww-display-image ()
103   (let ((data (buffer-substring (point) (point-max))))
104     (eww-setup-buffer)
105     (let ((inhibit-read-only t))
106       (shr-put-image data nil))
107     (goto-char (point-min))))
108
109 (defun eww-setup-buffer ()
110   (pop-to-buffer (get-buffer-create "*eww*"))
111   (remove-overlays)
112   (setq widget-field-list nil)
113   (let ((inhibit-read-only t))
114     (erase-buffer))
115   (eww-mode))
116
117 (defvar eww-mode-map
118   (let ((map (make-sparse-keymap)))
119     (suppress-keymap map)
120     (define-key map "q" 'eww-quit)
121     (define-key map [tab] 'widget-forward)
122     (define-key map [backtab] 'widget-backward)
123     (define-key map [delete] 'scroll-down-command)
124     (define-key map "\177" 'scroll-down-command)
125     (define-key map " " 'scroll-up-command)
126     (define-key map "p" 'eww-previous-url)
127     ;;(define-key map "n" 'eww-next-url)
128     map))
129
130 (defun eww-mode ()
131   "Mode for browsing the web.
132
133 \\{eww-mode-map}"
134   (interactive)
135   (setq major-mode 'eww-mode
136         mode-name "eww")
137   (set (make-local-variable 'eww-current-url) 'author)
138   (set (make-local-variable 'browse-url-browser-function) 'eww-browse-url)
139   ;;(setq buffer-read-only t)
140   (use-local-map eww-mode-map))
141
142 (defun eww-browse-url (url &optional new-window)
143   (push (list eww-current-url (point))
144         eww-history)
145   (eww url))
146
147 (defun eww-quit ()
148   "Exit the Emacs Web Wowser."
149   (interactive)
150   (setq eww-history nil)
151   (kill-buffer (current-buffer)))
152
153 (defun eww-previous-url ()
154   "Go to the previously displayed page."
155   (interactive)
156   (when (zerop (length eww-history))
157     (error "No previous page"))
158   (let ((prev (pop eww-history)))
159     (url-retrieve (car prev) 'eww-render (list (car prev) (cadr prev)))))
160
161 ;; Form support.
162
163 (defvar eww-form nil)
164
165 (defun eww-tag-form (cont)
166   (let ((eww-form
167          (list (assq :method cont)
168                (assq :action cont)))
169         (start (point)))
170     (shr-ensure-paragraph)
171     (shr-generic cont)
172     (shr-ensure-paragraph)
173     (put-text-property start (1+ start)
174                        'eww-form eww-form)))
175
176 (defun eww-tag-input (cont)
177   (let ((start (point))
178         (widget (list
179                  'editable-field
180                  :size (string-to-number
181                         (or (cdr (assq :size cont))
182                             "40"))
183                  :value (or (cdr (assq :value cont)) "")
184                  :action 'eww-submit
185                  :name (cdr (assq :name cont))
186                  :eww-form eww-form)))
187     (apply 'widget-create widget)
188     (shr-generic cont)
189     (put-text-property start (point) 'eww-widget widget)))
190
191 (defun eww-submit (widget dummy)
192   (let ((form (getf (cdr widget) :eww-form))
193         values)
194     (dolist (overlay (overlays-in (point-min) (point-max)))
195       (let ((field (getf (overlay-properties overlay) 'field)))
196         (when (eq (getf (cdr field) :eww-form) form)
197           (let ((name (getf (cdr field) :name)))
198             (when name
199               (push (cons name (widget-value field))
200                     values))))))
201     (let ((shr-base eww-current-url))
202       (eww-browse-url
203        (shr-expand-url
204         (concat
205          (getf form :action)
206          "?"
207          (mm-url-encode-www-form-urlencoded values)))))))
208
209 (defun eww-convert-widgets ()
210   (let ((start (point-min))
211         widget)
212     (while (setq start (next-single-property-change start 'eww-widget))
213       (setq widget (get-text-property start 'eww-widget))
214       (goto-char start)
215       (delete-region start (next-single-property-change start 'eww-widget))
216       (apply 'widget-create widget)
217       (put-text-property start (point) 'not-read-only t))
218     (widget-setup)))
219
220 (provide 'eww)
221
222 ;;; eww.el ends here