;;; -*- mode: Emacs-Lisp; coding: euc-japan -*-

;; Copyright (C) 2000 TSUCHIYA Masatoshi <tsuchiya@pine.kuee.kyoto-u.ac.jp>

;; Authors: TSUCHIYA Masatoshi <tsuchiya@pine.kuee.kyoto-u.ac.jp>,
;;          Shun-ichi GOTO     <gotoh@taiyo.co.jp>,
;;          Satoru Takabayashi <satoru-t@is.aist-nara.ac.jp>
;;          Hideyuki SHIRAI    <shirai@meadowy.org>
;; Keywords: w3m, WWW, hypermedia

;; w3m.el is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.

;; w3m.el is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with w3m.el; if not, write to the Free Software Foundation,
;; Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


;;; Commentary:

;; w3m.el is the interface program of w3m on Emacs.  For more detail
;; about w3m, see:
;;
;;    http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/
;;

;;; How to install:

;; Please put this file to appropriate directory, and if you want
;; byte-compile it.  And add following lisp expressions to your
;; ~/.emacs.
;;
;;     (autoload 'w3m "w3m" "Interface for w3m on Emacs." t)


;;; Code:

(or (and (boundp 'emacs-major-version)
	 (>= emacs-major-version 20))
    (progn
      (require 'poe)
      (require 'pcustom)))

(defgroup w3m nil
  "w3m - the web browser of choice."
  :group 'hypermedia)

(defgroup w3m-face nil
  "Faces for w3m."
  :group 'w3m
  :prefix "w3m-")

(defcustom w3m-command "w3m"
  "*Name of the executable file of w3m."
  :group 'w3m
  :type 'string)

(defcustom w3m-command-arguments '("-e" "-halfdump" "-cols" col url)
  "*Arguments of w3m."
  :group 'w3m
  :type '(repeat (restricted-sexp :match-alternatives (stringp 'col 'url))))

(defcustom w3m-viewer-command "xv"
  "*Name of the viewer."
  :group 'w3m
  :type 'string)

(defcustom w3m-viewer-command-arguments '(file)
  "Arguments of viewer."
  :group 'w3m
  :type '(repeat (restricted-sexp :match-alternatives (stringp 'file))))

(defcustom w3m-browser-command "netscape"
  "*Command name or function name of the browser."
  :group 'w3m
  :type '(choice (string :tag "Name of command")
		 (function :tag "Name of function")))

(defcustom w3m-browser-command-arguments '(url)
  "*Arguments of browser."
  :group 'w3m
  :type '(repeat (restricted-sexp :match-alternatives (stringp 'url))))

(defcustom w3m-mailto-url-function nil
  "*Mailto handling Function."
  :group 'w3m
  :type 'function)

(defcustom w3m-use-cygdrive t
  "*If non-nil, use /cygdrive/ rule when expand-file-name."
  :group 'w3m
  :type 'boolean)

(defcustom w3m-default-save-dir "~/.w3m"
  "*Default directory for save file."
  :group 'w3m
  :type 'directory)

(defcustom w3m-coding-system (if (boundp 'MULE) '*euc-japan* 'euc-japan)
  "*Coding system for w3m."
  :group 'w3m
  :type 'symbol)

(defcustom w3m-bookmark-file (expand-file-name "~/.w3m/bookmark.html")
  "*Bookmark file of w3m."
  :group 'w3m
  :type 'file)

(defcustom w3m-arrived-list-file (expand-file-name "~/.w3m/.arrived")
  "*Arrived URL file of w3m."
  :group 'w3m
  :type 'file)

(defcustom w3m-arrived-ct-file (expand-file-name "~/.w3m/.ctcheck")
  "*Arrived URL's context-type file of w3m."
  :group 'w3m
  :type 'file)

(defcustom w3m-arrived-file-cs
  (if (boundp 'MULE) '*euc-japan*unix 'euc-japan-unix)
  "*Coding system for arrived file."
  :group 'w3m
  :type 'symbol)

(defcustom w3m-arrived-list-keep 500
  "*Arrived keep count of w3m."
  :group 'w3m
  :type 'integer)

(defcustom w3m-keep-backlog 300
  "*Back log size of w3m."
  :group 'w3m
  :type 'integer)

(defcustom w3m-fill-column (- (frame-width) 4)
  "*Fill column of w3m."
  :group 'w3m
  :type 'integer)

(defcustom w3m-always-html-url-regex nil
  "*If URL is matched this regex, it handle always Text/Html."
  :group 'w3m
  :type 'string)

(defface w3m-anchor-face
  '((((class color) (background light)) (:foreground "red" :underline t))
    (((class color) (background dark)) (:foreground "blue" :underline t))
    (t (:underline t)))
  "*Face to fontify anchors."
  :group 'w3m-face)

(defface w3m-arrived-anchor-face
  '((((class color) (background light))
     (:foreground "navy" :underline t :bold t))
    (((class color) (background dark))
     (:foreground "blue" :underline t :bold t))
    (t (:underline t)))
  "*Face to fontify anchors, if arrived."
  :group 'w3m-face)

(defface w3m-image-face
  '((((class color) (background light)) (:foreground "ForestGreen"))
    (((class color) (background dark)) (:foreground "PaleGreen"))
    (t (:underline t)))
  "*Face to fontify image alternate strings."
  :group 'w3m-face)

(defcustom w3m-hook nil
  "*Hook run before w3m called."
  :group 'w3m
  :type 'hook)

(defcustom w3m-mode-hook nil
  "*Hook run before w3m-mode called."
  :group 'w3m
  :type 'hook)

(defcustom w3m-fontify-before-hook nil
  "*Hook run before w3m-fontify called."
  :group 'w3m
  :type 'hook)

(defcustom w3m-fontify-after-hook nil
  "*Hook run after w3m-fontify called."
  :group 'w3m
  :type 'hook)

(defcustom w3m-process-type 'start-process
  "*Function type for w3m execution."
  :group 'w3m
  :type '(choice (symbol :tag "call-process" call-process)
		 (symbol :tag "start-process" start-process)))

(defcustom w3m-process-connection-type t
  "*Process connection type for w3m execution."
  :group 'w3m
  :type 'boolean)

(defvar w3m-current-url nil "URL of this buffer.")
(defvar w3m-current-title nil "Title of this buffer.")
(defvar w3m-url-history nil "History of URL.")

(defvar w3m-backlog-buffer nil)
(defvar w3m-backlog-articles nil)
(defvar w3m-backlog-hashtb nil)
(defvar w3m-input-url-history nil)

(defvar w3m-arrived-anchor-list nil)
(defvar w3m-arrived-url-ct nil)
(defvar w3m-arrived-user-list nil)

(defvar w3m-process nil)
(defvar w3m-process-string nil)
(defvar w3m-process-url nil)
(defvar w3m-process-user nil)
(defvar w3m-process-passwd nil)
(defvar w3m-process-user-counter 0)

(make-variable-buffer-local 'w3m-process)
(make-variable-buffer-local 'w3m-process-string)
(make-variable-buffer-local 'w3m-process-url)
(make-variable-buffer-local 'w3m-process-user)
(make-variable-buffer-local 'w3m-process-passwd)
(make-variable-buffer-local 'w3m-process-user-counter)

(defun w3m-arrived-list-load ()
  "Load arrived url list from 'w3m-arrived-list-file'
and 'w3m-arrived-ct-file'."
  (when (file-readable-p w3m-arrived-ct-file)
    (with-temp-buffer
      (let ((file-coding-system-for-read w3m-arrived-file-cs)
	    (coding-system-for-read w3m-arrived-file-cs))
	(insert-file-contents w3m-arrived-ct-file)
	(setq w3m-arrived-url-ct
	      (condition-case nil
		  (read (current-buffer))
		(error nil))))))
  (when (file-readable-p w3m-arrived-list-file)
    (with-temp-buffer
      (let ((file-coding-system-for-read w3m-arrived-file-cs)
	    (coding-system-for-read w3m-arrived-file-cs))
	(insert-file-contents w3m-arrived-list-file)
	(setq w3m-arrived-anchor-list
	      (condition-case nil
		  (read (current-buffer))
		(error nil)))))))

(defun w3m-arrived-list-save ()
  "Save arrived url list to 'w3m-arrived-list-file'
and 'w3m-arrived-ct-file'."
  (when (> (length w3m-arrived-url-ct) w3m-arrived-list-keep)
    (setq w3m-arrived-url-ct
	  (nreverse (nthcdr (- (length w3m-arrived-url-ct)
			       w3m-arrived-list-keep)
			    (nreverse w3m-arrived-url-ct)))))
  (when (and w3m-arrived-url-ct
	     (file-writable-p w3m-arrived-ct-file))
    (with-temp-buffer
      (let ((file-coding-system w3m-arrived-file-cs)
	    (coding-system-for-write w3m-arrived-file-cs))
	(prin1 w3m-arrived-url-ct (current-buffer))
	(princ "\n" (current-buffer))
	(write-region (point-min) (point-max)
		      w3m-arrived-ct-file nil 'nomsg))))
  (when (> (length w3m-arrived-anchor-list) w3m-arrived-list-keep)
    (setq w3m-arrived-anchor-list
	  (nreverse (nthcdr (- (length w3m-arrived-anchor-list)
			       w3m-arrived-list-keep)
			    (nreverse w3m-arrived-anchor-list)))))
  (when (and w3m-arrived-anchor-list
	     (file-writable-p w3m-arrived-list-file))
    (with-temp-buffer
      (let ((file-coding-system w3m-arrived-file-cs)
	    (coding-system-for-write w3m-arrived-file-cs))
	(prin1 w3m-arrived-anchor-list (current-buffer))
	(princ "\n" (current-buffer))
	(write-region (point-min) (point-max)
		      w3m-arrived-list-file nil 'nomsg)
	(setq w3m-arrived-anchor-list nil)))))

(defun w3m-arrived-list-add (&optional url)
  "Cons url to 'w3m-arrived-anchor-list'. CAR is newest."
  (setq url (or url w3m-current-url))
  (when (> (length url) 5) ;; ignore short
    (set-text-properties 0 (length url) nil url)
    (setq w3m-arrived-anchor-list
	  (cons url (delete url w3m-arrived-anchor-list)))))
	  
(defun w3m-fontify ()
  "Fontify this buffer."
  (let ((case-fold-search t))
    (run-hooks 'w3m-fontify-before-hook)
    ;; Delete extra title tag.
    (let (start)
      (and (search-forward "<title>" nil t)
	   (setq start (match-beginning 0))
	   (search-forward "</title>" nil t)
	   (delete-region start (match-end 0))))
    ;; Fontify bold characters.
    (goto-char (point-min))
    (while (search-forward "<b>" nil t)
      (let ((start (match-beginning 0)))
	(delete-region start (match-end 0))
	(when (search-forward "</b>" nil t)
	  (delete-region (match-beginning 0) (match-end 0))
	  (put-text-property start (match-beginning 0) 'face 'bold))))
    ;; Delete excessive `hseq' elements of anchor tags.
    (goto-char (point-min))
    (while (re-search-forward "<a\\( hseq=\"[-0-9]+\"\\)" nil t)
      (delete-region (match-beginning 1) (match-end 1)))
    ;; Re-ordering anchor elements.
    (goto-char (point-min))
    (let (href)
      (while (re-search-forward "<a\\([ \t\n]\\)[^>]+[ \t\n]href=\\(\"[^\"]*\"\\)" nil t)
	(setq href (buffer-substring (match-beginning 2) (match-end 2)))
	(delete-region (match-beginning 2) (match-end 2))
	(goto-char (match-beginning 1))
	(insert " href=" href)))
    ;; Fontify anchor tags.
    (goto-char (point-min))
    (while (re-search-forward
	    "<a\\([ \t\n]+href=\"\\([^\"]*\\)\"\\)?\\([ \t\n]+name=\"\\([^\"]*\\)\"\\)?[^>]*>"
	    nil t)
      (let ((url (match-string 2))
	    (tag (match-string 4))
	    (start (match-beginning 0))
	    (end))
	(delete-region start (match-end 0))
	(cond (url
	       (when (search-forward "</a>" nil t)
		 (delete-region (setq end (match-beginning 0)) (match-end 0))
		 (if (member (w3m-expand-url url w3m-current-url)
			     w3m-arrived-anchor-list)
		     (put-text-property start end 'face 'w3m-arrived-anchor-face)
		   (put-text-property start end 'face 'w3m-anchor-face))
		 (put-text-property start end 'w3m-href-anchor url))
	       (when tag
		 (put-text-property start end 'w3m-name-anchor tag)))
	      (tag
	       (when (re-search-forward "<\\|\n" nil t)
		 (setq end (match-beginning 0))
		 (put-text-property start end 'w3m-name-anchor tag))))))
    ;; Fontify image alternate strings.
    (goto-char (point-min))
    (while (re-search-forward "<img_alt src=\"\\([^\"]*\\)\">" nil t)
      (let ((src (match-string 1))
	    (start (match-beginning 0))
	    (end))
	(delete-region start (match-end 0))
	(when (search-forward "</img_alt>" nil t)
	  (delete-region (setq end (match-beginning 0)) (match-end 0))
	  (put-text-property start end 'face 'w3m-image-face)
	  (put-text-property start end 'w3m-image src))))
    ;; Remove other markups.
    (goto-char (point-min))
    (while (re-search-forward "</?[A-z][^>]*>" nil t)
      (delete-region (match-beginning 0) (match-end 0)))
    ;; Decode escaped characters.
    (goto-char (point-min))
    (let (prop)
      (while (re-search-forward
	      "&\\(\\(nbsp\\)\\|\\(gt\\)\\|\\(lt\\)\\|\\(amp\\)\\|\\(quot\\)\\|\\(apos\\)\\);"
	      nil t)
	(setq prop (text-properties-at (match-beginning 0)))
	(delete-region (match-beginning 0) (match-end 0))
	(insert (if (match-beginning 2) " "
		  (if (match-beginning 3) ">"
		    (if (match-beginning 4) "<"
		      (if (match-beginning 5) "&"
			(if (match-beginning 6) "\"" "'"))))))
	(if prop (add-text-properties (1- (point)) (point) prop))))
    (run-hooks 'w3m-fontify-after-hook)))


(defun w3m-refontify-anchor (&optional buff)
  "Change face 'w3m-anchor-face to 'w3m-arrived-anchor-face."
  (save-excursion
    (and buff (set-buffer buff))
    (when (and (eq major-mode 'w3m-mode)
	       (eq (get-text-property (point) 'face) 'w3m-anchor-face))
      (let* (start
	     (end (next-single-property-change (point) 'face))
	     (buffer-read-only nil))
	(when(and end
		  (setq start (previous-single-property-change end 'face)))
	  (put-text-property start end 'face 'w3m-arrived-anchor-face))
	(set-buffer-modified-p nil)))))


(defun w3m-input-url (&optional prompt default)
  "Read a URL from the minibuffer, prompting with string PROMPT."
  (let (url candidates)
    (w3m-backlog-setup)
    (or w3m-input-url-history
	(setq w3m-input-url-history (or w3m-arrived-anchor-list
					(w3m-arrived-list-load))))
    (mapatoms (lambda (x)
		(setq candidates (cons (cons (symbol-name x) x) candidates)))
	      w3m-backlog-hashtb)
    (setq url (completing-read (or prompt "URL: ")
			       candidates nil nil
			       default 'w3m-input-url-history default))
    ;; remove duplication
    (setq w3m-input-url-history (cons url (delete url w3m-input-url-history)))
    ;; return value
    url))


(defun w3m-backlog-setup ()
  "Initialize backlog variables."
  (unless (and (bufferp w3m-backlog-buffer)
	       (buffer-live-p w3m-backlog-buffer))
    (save-excursion
      (set-buffer (get-buffer-create " *w3m backlog*"))
      (buffer-disable-undo)
      (setq buffer-read-only t
	    w3m-backlog-buffer (current-buffer))))
  (unless w3m-backlog-hashtb
    (setq w3m-backlog-hashtb (make-vector 1021 0))))

(defun w3m-backlog-shutdown ()
  "Clear all backlog variables and buffers."
  (when (get-buffer w3m-backlog-buffer)
    (kill-buffer w3m-backlog-buffer))
  (setq w3m-backlog-hashtb nil
	w3m-backlog-articles nil))

(defun w3m-backlog-enter (url buffer)
  (w3m-backlog-setup)
  (let ((ident (intern url w3m-backlog-hashtb)))
    (if (memq ident w3m-backlog-articles)
	()				; It's already kept.
      ;; Remove the oldest article, if necessary.
      (and (numberp w3m-keep-backlog)
	   (>= (length w3m-backlog-articles) w3m-keep-backlog)
	   (w3m-backlog-remove-oldest))
      ;; Insert the new article.
      (save-excursion
	(set-buffer w3m-backlog-buffer)
	(let (buffer-read-only)
	  (goto-char (point-max))
	  (unless (bolp) (insert "\n"))
	  (let ((b (point)))
	    (insert-buffer-substring buffer)
	    ;; Tag the beginning of the article with the ident.
	    (when (> (point-max) b)
	      (put-text-property b (1+ b) 'w3m-backlog ident)
	      (setq w3m-backlog-articles (cons ident w3m-backlog-articles)))
	    ))))))

(defun w3m-backlog-remove-oldest ()
  (save-excursion
    (set-buffer w3m-backlog-buffer)
    (goto-char (point-min))
    (if (zerop (buffer-size))
	()				; The buffer is empty.
      (let ((ident (get-text-property (point) 'w3m-backlog))
	    buffer-read-only)
	;; Remove the ident from the list of articles.
	(when ident
	  (setq w3m-backlog-articles (delq ident w3m-backlog-articles)))
	;; Delete the article itself.
	(delete-region (point)
		       (next-single-property-change
			(1+ (point)) 'w3m-backlog nil (point-max)))))))

(defun w3m-backlog-remove (url)
  "Remove data of URL from the backlog."
  (w3m-backlog-setup)
  (let ((ident (intern url w3m-backlog-hashtb))
	beg end)
    (when (memq ident w3m-backlog-articles)
      ;; It was in the backlog.
      (save-excursion
	(set-buffer w3m-backlog-buffer)
	(let (buffer-read-only)
	  (when (setq beg (text-property-any
			   (point-min) (point-max) 'w3m-backlog ident))
	    ;; Find the end (i. e., the beginning of the next article).
	    (setq end (next-single-property-change
		       (1+ beg) 'w3m-backlog (current-buffer) (point-max)))
	    (delete-region beg end)))
	(setq w3m-backlog-articles (delq ident w3m-backlog-articles))))))

(defun w3m-backlog-request (url &optional buffer)
  (w3m-backlog-setup)
  (let ((ident (intern url w3m-backlog-hashtb)))
    (when (memq ident w3m-backlog-articles)
      ;; It was in the backlog.
      (let (beg end)
	(save-excursion
	  (set-buffer w3m-backlog-buffer)
	  (if (not (setq beg (text-property-any
			      (point-min) (point-max) 'w3m-backlog ident)))
	      ;; It wasn't in the backlog after all.
	      (setq w3m-backlog-articles (delq ident w3m-backlog-articles))
	    ;; Find the end (i. e., the beginning of the next article).
	    (setq end
		  (next-single-property-change
		   (1+ beg) 'w3m-backlog (current-buffer) (point-max)))))
	(and beg
	     end
	     (save-excursion
	       (and buffer (set-buffer buffer))
	       (let (buffer-read-only)
		 (insert-buffer-substring w3m-backlog-buffer beg end))
	       t))))))

(defun w3m-exec (url &optional buffer ct)
  "Download URL with w3m to the BUFFER.
If BUFFER is nil, all data is placed to the current buffer.
CT denotes content-type."
  (let ((cbuf (current-buffer)))
    (when (let ((args (copy-sequence w3m-command-arguments)))
	    (cond
	     ;; backlog exist.
	     ((w3m-backlog-request url)
	      (w3m-exec-w3m url args buffer) nil)
	     ;; ange|efs-ftp 
	     ((and (string-match "^ftp://" url)
		   (not (string-match "\\.s?html?$" url)))
	      (w3m-exec-ftp url) t)
	     ;; text/html
	     ((or (string-match "\\.s?html?$\\|/$" url)
		  (and w3m-always-html-url-regex
		       (string-match w3m-always-html-url-regex url))
		  (eq ct 'text/html))
	      (w3m-exec-w3m url args buffer) nil)
	     ;; text/*
	     ((or (string-match "\\.\\(txt\\|el\\)$" url) (eq ct 'text))
	      (setq args (cons "-dump" (delete "-halfdump" args)))
	      (w3m-exec-w3m url args buffer) nil)
	     ;; image/*
	     ((eq ct 'image)
	      (require 'w3)
	      (w3-fetch url) t)
	     ;; application/*, audio/*, etc...
	     ((eq ct 'application)
	      (require 'w3)
	      (let ((mm-download-directory
		     (file-name-as-directory w3m-default-save-dir)))
		(w3-download-url (w3m-expand-url url w3m-current-url))) t)
	     ;; get context-type and w3-exec() call recursion .
	     ((not ct)
	      (w3m-exec url buffer (w3m-exec-w3m-ctcheck url)))
	     ;; error
	     (t (error "context-type check error."))))
      ;; if not exec w3m, return (current-buffer)
      cbuf)))


(defun w3m-exec-w3m-ctcheck (url)
  (or (cdr (assoc url w3m-arrived-url-ct))
      (save-excursion
	(message "Dump header...")
	(set-buffer (get-buffer-create " *w3m ctcheck*"))
	(buffer-disable-undo)
	(delete-region (point-min) (point-max))
	(let ((args (copy-sequence w3m-command-arguments))
	      (case-fold-search t)
	      (ct 'error))
	  (setq args (cons "-dump_head" (delete "-halfdump" args)))
	  (w3m-exec-process url args)
	  (message "Dump header... done.")
	  (goto-char (point-min))
	  (when (re-search-forward "^content-type: " nil t)
	    (setq ct (if (looking-at "text/html") 'text/html
		       (if (looking-at "text") 'text
			 (if (looking-at "image") 'image
			   'application))))
	    (setq w3m-arrived-url-ct (cons (cons url ct) w3m-arrived-url-ct))
	    ct)))))

(defun w3m-exec-w3m (url args buffer)
  (save-excursion
    (setq buffer-read-only nil)
    (if buffer (set-buffer buffer))
    (delete-region (point-min) (point-max))
    (unless (w3m-backlog-request url)
      (message "Loading page...")
      (w3m-exec-process url args)
      (message "Loading page... done."))
    (w3m-backlog-enter url (current-buffer))
    ;; Setting buffer local variables.
    (set (make-local-variable 'w3m-current-url) url)
    (goto-char (point-min))
    (let (title)
      (mapcar (lambda (regexp)
		(goto-char 1)
		(when (re-search-forward regexp nil t)
		  (setq title (match-string 1))
		  (delete-region (match-beginning 0) (match-end 0))))
	      '("<title_alt[ \t\n]+title=\"\\([^\"]+\\)\">"
		"<title>\\([^<]\\)</title>"))
      (if (and (null title)
	       (< 0 (length (file-name-nondirectory url))))
	  (setq title (file-name-nondirectory url)))
      (set (make-local-variable 'w3m-current-title) (or title "<no-title>")))
    (set (make-local-variable 'w3m-url-history)
	 (cons url w3m-url-history))
    (setq-default w3m-url-history
		  (cons url (default-value 'w3m-url-history)))))


(defun w3m-exec-ftp (url)
  (let ((ftp (w3m-convert-ftp-to-emacsen url))
	(file (file-name-nondirectory url)))
    (if (string-match "\\(\\.gz\\|\\.bz2\\|\\.zip\\|\\.lzh\\)$" file)
	(copy-file ftp (w3m-read-file-name nil nil file))
      (dired-other-window ftp))))


(defun w3m-convert-ftp-to-emacsen (url)
  (or (and (string-match "^ftp://?\\([^/@]+@\\)?\\([^/]+\\)\\(/~/\\)?" url)
	   (concat "/"
		   (if (match-beginning 1)
		       (substring url (match-beginning 1) (match-end 1))
		     "anonymous@")
		   (substring url (match-beginning 2) (match-end 2))
		   ":"
		   (substring url (match-end 2))))
      (error "URL is strange.")))

(defun w3m-exec-process (url args)
  (save-excursion
    (let ((coding-system-for-read w3m-coding-system)
	  (coding-system-for-write w3m-coding-system)
	  (default-process-coding-system
	    (cons w3m-coding-system w3m-coding-system))
	  (process-connection-type w3m-process-connection-type))
      (if (eq w3m-process-type 'start-process)
	  ;; start-process
	  (unwind-protect nil
	    (let ()
	      ;; (pop-to-buffer (current-buffer))
	      (setq w3m-process-url url)
	      (setq w3m-process-string nil)
	      (setq w3m-process-user nil)
	      (setq w3m-process-passwd nil)
	      (setq w3m-process-user-counter 2)
	      (setq buffer-read-only t)
	      (setq w3m-process
		    (apply 'start-process w3m-command (current-buffer) w3m-command
			   (mapcar (lambda (arg)
				     (if (eq arg 'col)
					 (format "%d" w3m-fill-column)
				       (eval arg)))
				   args)))
	      (set-process-coding-system w3m-process w3m-coding-system)
	      (set-process-filter w3m-process 'w3m-exec-filter)
	      (set-process-sentinel w3m-process 'w3m-exec-sentinel)
	      (process-kill-without-query w3m-process)
	      (while w3m-process
		(sit-for 0.5)
		(discard-input)))
	    (setq w3m-process nil)
	    (setq w3m-process-url url)
	    (setq w3m-process-string nil)
	    (setq w3m-process-user nil)
	    (setq w3m-process-passwd nil)
	    (setq w3m-process-user-counter 0)
	    (setq buffer-read-only nil))
	;; call-process
	(apply 'call-process w3m-command nil t nil
	       (mapcar (lambda (arg)
			 (if (eq arg 'col)
			     (format "%d" w3m-fill-column)
			   (eval arg)))
		       args))))))

(defun w3m-exec-filter (process string)
  (if (bufferp (process-buffer process))
      (let ((obuf (buffer-name)))
	(unwind-protect
	    (progn
	      (set-buffer (process-buffer process))
	      (let ((buffer-read-only nil)
		    (case-fold-search nil)
		    file input prompt)
		(goto-char (point-max))
		(setq w3m-process-string
		      (concat w3m-process-string string))
		(while (string-match "\n" w3m-process-string)
		  (insert (concat
			   (substring w3m-process-string 0 (match-beginning 0))
			   "\n"))
		  (setq w3m-process-string
			(substring w3m-process-string (match-end 0))))
		(cond
		 ;; username
		 ((string-match "^Username: " w3m-process-string)
		  (setq prompt (match-string 0 w3m-process-string))
		  (setq w3m-process-string "")
		  (setq w3m-process-user
			(or (nth 0 (w3m-exec-get-user w3m-process-url))
			    (read-from-minibuffer prompt)))
		  (process-send-string process (concat w3m-process-user "\n")))
		 ;; passwd
		 ((string-match "^Password: " w3m-process-string)
		  (setq prompt (match-string 0 w3m-process-string))
		  (setq w3m-process-string "")
		  (setq w3m-process-passwd
			(or (nth 1 (w3m-exec-get-user w3m-process-url))
			    (w3m-read-passwd prompt)))
		  (process-send-string process (concat w3m-process-passwd "\n")))
		 ;; save file
		 ((string-match "Save file to:" w3m-process-string)
		  (setq w3m-process-string "")
		  (setq input (w3m-read-file-name nil nil w3m-process-url))
		  (process-send-string process (concat input "\n"))
		  (insert (format "Save to %s.\n" input)))
		 ;; overwrite
		 ((string-match "File exists. Overwrite? (y or n)" w3m-process-string)
		  (setq w3m-process-string "")
		  (condition-case nil
		      (process-send-string process "y\n")
		    (error nil)))
		 ;; quit
		 ((string-match " *Hit any key to quit w3m:" w3m-process-string)
		  (condition-case nil
		      (quit-process process)
		    (error nil))))))
	  (if (get-buffer obuf)
	      (set-buffer obuf))))))

(defun w3m-exec-get-user (url)
  (if (= w3m-process-user-counter 0)
      nil
    (let ((urllist w3m-arrived-user-list))
      (catch 'get
	(while urllist
	  (when (string-match (concat "^"
				      (regexp-quote (car (car urllist))))
			      url)
	    (setq w3m-process-user-counter (1- w3m-process-user-counter))
	    (throw 'get (cdr (car urllist))))
	  (setq urllist (cdr urllist)))))))

(defun w3m-exec-sentinel (process event)
  (if (bufferp (process-buffer process))
      (let ((obuf (buffer-name)))
	(unwind-protect
	    (progn
	      (set-buffer (process-buffer process))
	      (if (and w3m-process-url w3m-process-user)
		  (setq w3m-arrived-user-list
			(cons
			 (cons w3m-process-url
			       (list w3m-process-user w3m-process-passwd))
			 (delete (assoc w3m-process-url w3m-arrived-user-list)
				 w3m-arrived-user-list))))
	      (setq w3m-process-string nil)
	      (setq w3m-process nil)
	      (setq w3m-process-url nil)
	      (setq w3m-process-user nil)
	      (setq w3m-process-passwd nil))
	  (if (get-buffer obuf)
	      (set-buffer obuf))))))

(defun w3m-read-file-name (&optional prompt dir default existing initial)
  (let* ((default (and default (file-name-nondirectory default)))
	 (prompt (or prompt
		     (if default (format "Save to (%s): " default) "Save to: ")))
	 (initial (or initial default))
	 (dir (file-name-as-directory (or dir w3m-default-save-dir)))
	 (default-directory dir)
	 (file (read-file-name prompt dir default existing initial)))
    (if (not (file-directory-p file))
	(setq w3m-default-save-dir
	      (or (file-name-directory file) w3m-default-save-dir))
      (setq w3m-default-save-dir file)
      (if default
	  (setq file (expand-file-name default file))))
    (expand-file-name file)))

(defun w3m-read-passwd (prompt)
  (let ((inhibit-input-event-recording t))
    (if (fboundp 'read-passwd)
	(condition-case nil
	    (read-passwd prompt)
	  (error ""))
      (let ((pass "")
	    (c 0)
	    (echo-keystrokes 0)
	    (ociea cursor-in-echo-area))
	(condition-case nil
	    (progn
	      (setq cursor-in-echo-area 1)
	      (while (and (/= c ?\r) (/= c ?\n) (/= c ?\e) (/= c 7)) ;; ^G
		(message "%s%s"
			 prompt
			 (make-string (length pass) ?.))
		(setq c (read-char-exclusive))
		(cond
		 ((char-equal c ?\C-u)
		  (setq pass ""))
		 ((or (char-equal c ?\b) (char-equal c ?\177))  ;; BS DELL
		  ;; delete one character in the end
		  (if (not (equal pass ""))
		      (setq pass (substring pass 0 -1))))
		 ((< c 32) ()) ;; control, just ignore
		 (t
		  (setq pass (concat pass (char-to-string c))))))
	      (setq cursor-in-echo-area -1))
	  (quit
	   (setq cursor-in-echo-area ociea)
	   (signal 'quit nil))
	  (error
	   ;; Probably not happen. Just align to the code above.
	   (setq pass "")))
	(setq cursor-in-echo-area ociea)
	(message "")
	(sit-for 0)
	pass))))

(defun w3m-search-name-anchor (name &optional quiet)
  (interactive "sName: ")
  (let ((pos (point-min)))
    (catch 'found
      (while (setq pos (next-single-property-change pos 'w3m-name-anchor))
	(when (equal name (get-text-property pos 'w3m-name-anchor))
	  (goto-char pos)
	  (throw 'found t))
	(setq pos (next-single-property-change pos 'w3m-name-anchor)))
      (unless quiet
	(message "Not found such name anchor."))
      nil)))


(defun w3m-save-position (url)
  (if url
      (let ((ident (intern-soft url w3m-backlog-hashtb)))
	(when ident
	  (set ident (cons (window-start) (point)))))))

(defun w3m-restore-position (url)
  (let ((ident (intern-soft url w3m-backlog-hashtb)))
    (when (and ident (boundp ident))
      (set-window-start nil (car (symbol-value ident)))
      (goto-char (cdr (symbol-value ident))))))


(defun w3m-view-previous-page (&optional arg)
  (interactive "p")
  (unless arg (setq arg 1))
  (let ((url (nth arg w3m-url-history)))
    (when url
      (let (w3m-url-history) (w3m-goto-url url))
      ;; restore last position
      (w3m-restore-position url)
      (setq w3m-url-history
	    (nthcdr arg w3m-url-history)))))

(defun w3m-view-previous-point ()
  (interactive)
  (w3m-restore-position w3m-current-url))

(defun w3m-expand-url (url base)
  "Convert URL to absolute, and canonicalize it."
  (if (not base) (setq base ""))
  (if (string-match "^[^:]+://[^/]*$" base)
      (setq base (concat base "/")))
  (cond
   ;; URL is relative on BASE.
   ((string-match "^#" url)
    (concat base url))
   ;; URL has absolute spec.
   ((string-match "^[^:]+:" url)
    url)
   ((string-match "^/" url)
    (if (string-match "^\\([^:]+://[^/]*\\)/" base)
	(concat (match-string 1 base) url)
      url))
   (t
    (let ((server "") path)
      (if (string-match "^\\([^:]+://[^/]*\\)/" base)
	  (setq server (match-string 1 base)
		base (substring base (match-end 1))))
      (setq path (expand-file-name url (file-name-directory base)))
      ;; remove drive (for Win32 platform)
      (if (string-match "^.:" path)
	  (setq path (substring path (match-end 0))))
      (concat server path)))))


(defun w3m-view-this-url (arg)
  "*View the URL of the link under point."
  (interactive "P")
  (let ((url (get-text-property (point) 'w3m-href-anchor)))
    (if url (w3m-goto-url (w3m-expand-url url w3m-current-url) arg))))

(defun w3m-mouse-view-this-url (event)
  (interactive "e")
  (mouse-set-point event)
  (call-interactively (function w3m-view-this-url)))

(defun w3m-view-image ()
  "*View the image under point."
  (interactive)
  (let ((file (get-text-property (point) 'w3m-image)))
    (if (not file)
	(message "No file at point.")
      (require 'w3)
      (w3-fetch (w3m-expand-url file w3m-current-url)))))


(defun w3m-save-image ()
  "*Save the image under point to a file."
  (interactive)
  (let ((file (get-text-property (point) 'w3m-image)))
    (if (not file)
	(message "No file at point.")
      (require 'w3)
      (let ((mm-download-directory
	     (file-name-as-directory w3m-default-save-dir)))
	(w3-download-url (w3m-expand-url file w3m-current-url))))))


(defun w3m-view-current-url-with-external-browser ()
  "*View this URL."
  (interactive)
  (let ((buffer (get-buffer-create " *w3m-view*"))
	(url (get-text-property (point) 'w3m-href-anchor)))
    (if url
	(setq url (w3m-expand-url url w3m-current-url))
      (if (y-or-n-p (format "Browse <%s> ? " w3m-current-url))
	  (setq url w3m-current-url)))
    (when url
      (message "Browse <%s>" url)
      (if (and (symbolp w3m-browser-command)
	       (fboundp w3m-browser-command))
	  (funcall w3m-browser-command url)
	(apply 'start-process
	       "w3m-external-browser"
	       buffer
	       w3m-browser-command
	       (mapcar (function eval)
		       w3m-browser-command-arguments))))))


(defun w3m-download-this-url ()
  "*Download the URL of the link under point to a file."
  (interactive)
  (let ((url (get-text-property (point) 'w3m-href-anchor)))
    (if (not url)
	(message "No URL at point.")
      (require 'w3)
      (let ((mm-download-directory
	     (file-name-as-directory w3m-default-save-dir)))
	(w3-download-url (w3m-expand-url url w3m-current-url)))
      (w3m-refontify-anchor (current-buffer)))))


(defun w3m-print-current-url ()
  "*Print the URL of current page and push it into kill-ring."
  (interactive)
  (kill-new w3m-current-url)
  (message "%s" w3m-current-url))

(defun w3m-print-this-url ()
  "*Print the URL of the link under point."
  (interactive)
  (let ((url (get-text-property (point) 'w3m-href-anchor)))
    (if url
	(kill-new (setq url (w3m-expand-url url w3m-current-url))))
    (message "%s" (or url "Not found."))))


(defun w3m-next-anchor (&optional arg)
  "*Move cursor to the next anchor."
  (interactive "p")
  (unless arg (setq arg 1))
  (if (< arg 0)
      ;; If ARG is negative.
      (w3m-previous-anchor (- arg))
    (when (get-text-property (point) 'w3m-href-anchor)
      (goto-char (next-single-property-change (point) 'w3m-href-anchor)))
    (while (and
	    (> arg 0)
	    (setq pos (next-single-property-change (point) 'w3m-href-anchor)))
      (goto-char pos)
      (unless (zerop (setq arg (1- arg)))
	(goto-char (next-single-property-change (point) 'w3m-href-anchor))))))


(defun w3m-previous-anchor (&optional arg)
  "Move cursor to the previous anchor."
  (interactive "p")
  (unless arg (setq arg 1))
  (if (< arg 0)
      ;; If ARG is negative.
      (w3m-next-anchor (- arg))
    (when (get-text-property (point) 'w3m-href-anchor)
      (goto-char (previous-single-property-change (1+ (point)) 'w3m-href-anchor)))
    (while (and
	    (> arg 0)
	    (setq pos (previous-single-property-change (point) 'w3m-href-anchor)))
      (goto-char (previous-single-property-change pos 'w3m-href-anchor))
      (setq arg (1- arg)))))


(defun w3m-expand-file-name (file)
  (setq file (expand-file-name file))
  (if (string-match "^\\(.\\):\\(.*\\)" file)
      (if w3m-use-cygdrive
	  (concat "/cygdrive/" (match-string 1 file) (match-string 2 file))
	(concat "file://" (match-string 1 file) (match-string 2 file)))
    file))


(defun w3m-view-bookmark ()
  (interactive)
  (if (file-readable-p w3m-bookmark-file)
      (w3m-goto-url (w3m-expand-file-name w3m-bookmark-file))))


(defun w3m-copy-buffer (buf &optional newname and-pop) "\
Create a twin copy of the current buffer.
if NEWNAME is nil, it defaults to the current buffer's name.
if AND-POP is non-nil, the new buffer is shown with `pop-to-buffer'."
  (interactive (list (current-buffer)
		     (if current-prefix-arg (read-string "Name: "))
		     t))
  (setq newname (or newname (buffer-name)))
  (if (string-match "<[0-9]+>\\'" newname)
      (setq newname (substring newname 0 (match-beginning 0))))
  (with-current-buffer buf
    (let ((ptmin (point-min))
	  (ptmax (point-max))
	  (content (save-restriction (widen) (buffer-string)))
	  (mode major-mode)
	  (lvars (buffer-local-variables))
	  (new (generate-new-buffer (or newname (buffer-name)))))
      (with-current-buffer new
	;;(erase-buffer)
	(insert content)
	(narrow-to-region ptmin ptmax)
	(funcall mode)			;still needed??  -sm
	(mapcar (lambda (v)
		  (if (not (consp v)) (makunbound v)
		    (condition-case ()	;in case var is read-only
			(set (make-local-variable (car v)) (cdr v))
		      (error nil))))
		lvars)
	(when and-pop (pop-to-buffer new))
	new))))


(defvar w3m-mode-map nil)
(unless w3m-mode-map
  (setq w3m-mode-map (make-keymap))
  (define-key w3m-mode-map " " 'scroll-up)
  (define-key w3m-mode-map "b" 'scroll-down)
  (define-key w3m-mode-map [backspace] 'scroll-down)
  (define-key w3m-mode-map [delete] 'scroll-down)
  (define-key w3m-mode-map "h" 'backward-char)
  (define-key w3m-mode-map "j" 'next-line)
  (define-key w3m-mode-map "k" 'previous-line)
  (define-key w3m-mode-map "l" 'forward-char)
  (define-key w3m-mode-map "J" (lambda () (interactive) (scroll-up 1)))
  (define-key w3m-mode-map "K" (lambda () (interactive) (scroll-up -1)))
  (define-key w3m-mode-map "G" 'goto-line)
  (define-key w3m-mode-map "\C-?" 'scroll-down)
  (define-key w3m-mode-map "\t" 'w3m-next-anchor)
  (define-key w3m-mode-map [down] 'w3m-next-anchor)
  (define-key w3m-mode-map "\M-\t" 'w3m-previous-anchor)
  (define-key w3m-mode-map [up] 'w3m-previous-anchor)
  (define-key w3m-mode-map "\C-m" 'w3m-view-this-url)
  (define-key w3m-mode-map [right] 'w3m-view-this-url)
  (if (featurep 'xemacs)
      (define-key w3m-mode-map [(button2)] 'w3m-mouse-view-this-url)
    (define-key w3m-mode-map [mouse-2] 'w3m-mouse-view-this-url))
  (define-key w3m-mode-map "\C-c\C-b" 'w3m-view-previous-point)
  (define-key w3m-mode-map [left] 'w3m-view-previous-page)
  (define-key w3m-mode-map "B" 'w3m-view-previous-page)
  (define-key w3m-mode-map "d" 'w3m-download-this-url)
  (define-key w3m-mode-map "u" 'w3m-print-this-url)
  (define-key w3m-mode-map "I" 'w3m-view-image)
  (define-key w3m-mode-map "\M-I" 'w3m-save-image)
  (define-key w3m-mode-map "c" 'w3m-print-current-url)
  (define-key w3m-mode-map "M" 'w3m-view-current-url-with-external-browser)
  (define-key w3m-mode-map "g" 'w3m)
  (define-key w3m-mode-map "U" 'w3m)
  (define-key w3m-mode-map "V" 'w3m)
  (define-key w3m-mode-map "v" 'w3m-view-bookmark)
  (define-key w3m-mode-map "q" 'w3m-quit)
  (define-key w3m-mode-map "Q" (lambda () (interactive) (w3m-quit t)))
  (define-key w3m-mode-map "\M-n" 'w3m-copy-buffer)
  (define-key w3m-mode-map "R" 'w3m-reload-this-page)
  (define-key w3m-mode-map "?" 'describe-mode)
  )


(defun w3m-quit (&optional force)
  (interactive "P")
  (when (or force
	    (y-or-n-p "Do you want to exit w3m? "))
    (kill-buffer (current-buffer))
    (w3m-arrived-list-save)
    (or (save-excursion
	  ;; Check existing w3m buffers.
	  (delq nil (mapcar (lambda (b)
			      (set-buffer b)
			      (eq major-mode 'w3m-mode))
			    (buffer-list))))
	;; If no w3m buffer exists, then destruct all cache.
	(w3m-backlog-shutdown))))


(defun w3m-mode ()
  "\\<w3m-mode-map>
   Major mode to browsing w3m buffer.

\\[w3m-view-this-url]	View this url.
\\[w3m-mouse-view-this-url]	View this url.
\\[w3m-reload-this-page]	Reload this page.
\\[w3m-next-anchor]	Jump next anchor.
\\[w3m-previous-anchor]	Jump previous anchor.
\\[w3m-view-previous-page]	Back to previous page.

\\[w3m-download-this-url]	Download this url.
\\[w3m-print-this-url]	Print this url.
\\[w3m-view-image]	View image.
\\[w3m-save-image]	Save image.

\\[w3m-print-current-url]	Print current url.
\\[w3m-view-current-url-with-external-browser]	View current url with external browser.

\\[scroll-up]	Scroll up.
\\[scroll-down]	Scroll down.

\\[next-line]	Next line.
\\[previous-line]	Previous line.

\\[forward-char]	Forward char.
\\[backward-char]	Backward char.

\\[goto-line]	Jump to line.
\\[w3m-view-previous-point]	w3m-view-previous-point.

\\[w3m]	w3m.
\\[w3m-view-bookmark]	w3m-view-bookmark.
\\[w3m-copy-buffer]	w3m-copy-buffer.

\\[w3m-quit]	w3m-quit.
\\[describe-mode]	describe-mode.
"
  (kill-all-local-variables)
  (buffer-disable-undo)
  (setq major-mode 'w3m-mode
	mode-name "w3m")
  (use-local-map w3m-mode-map)
  (run-hooks 'w3m-mode-hook))

(defun w3m-mailto-url (url)
  (if (and (symbolp w3m-mailto-url-function)
	   (fboundp w3m-mailto-url-function))
      (funcall w3m-mailto-url-function url)
    (let (comp)
      ;; Require `mail-user-agent' setting
      (if (not (and (boundp 'mail-user-agent)
		    mail-user-agent
		    (setq comp (intern-soft (concat (symbol-name mail-user-agent)
						    "-compose")))
		    (fboundp comp)))
	  (error "You must specify valid `mail-user-agent'."))
      ;; Use rfc2368.el if exist.
      ;; rfc2368.el is written by Sen Nagata.
      ;; You can find it in "contrib" directory of Mew package
      ;; or in "utils" directory of Wanderlust package.
      (if (or (featurep 'rfc2368)
	      (condition-case nil (require 'rfc2368) (error nil)))
	  (let ((info (rfc2368-parse-mailto-url url)))
	    (apply comp (mapcar (lambda (x)
				  (cdr (assoc x info)))
				'("To" "Subject"))))
	;; without rfc2368.el.
	(funcall comp (match-string 1 url))))))


(defun w3m-goto-url (url &optional reload)
  "Retrieve URL and display it in this buffer."
  (let (name buff)
    (if reload
	(w3m-backlog-remove url))
    (cond
     ;; process mailto: protocol
     ((string-match "^mailto:\\(.*\\)" url)
      (w3m-mailto-url url))
     (t
      (when (string-match "#\\([^#]+\\)$" url)
	(setq name (match-string 1 url)
	      url (substring url 0 (match-beginning 0))))
      (w3m-save-position w3m-current-url)
      (or w3m-arrived-anchor-list (w3m-arrived-list-load))
      (w3m-arrived-list-add url)
      (if (setq buff (w3m-exec url))
	  ;; no w3m exec and return *w3m* buffer.
	  (w3m-refontify-anchor buff)
	;; w3m exec.
	(w3m-fontify)
	(setq buffer-read-only t)
	(set-buffer-modified-p nil)
	(or (and name (w3m-search-name-anchor name))
	    (goto-char (point-min))))))))


(defun w3m-reload-this-page ()
  "Reload current page without cache."
  (interactive)
  (setq w3m-url-history (cdr w3m-url-history))
  (w3m-goto-url w3m-current-url 'reload))


(defun w3m (url &optional args)
  "Interface for w3m on Emacs."
  (interactive (list (w3m-input-url)))
  (set-buffer (get-buffer-create "*w3m*"))
  (or (eq major-mode 'w3m-mode)
      (w3m-mode))
  (setq mode-line-buffer-identification
	(list "%12b" " / " 'w3m-current-title))
  (if (string= url "")
      (w3m-view-bookmark)
    (w3m-goto-url url))
  (switch-to-buffer (current-buffer))
  (run-hooks 'w3m-hook))


(defun w3m-browse-url (url &optional new-window)
  "w3m interface function for browse-url.el."
  (interactive
   (progn
     (require 'browse-url)
     (browse-url-interactive-arg "w3m URL: ")))
  (if new-window (split-window))
  (w3m url))

(defun w3m-find-file (file)
  "w3m Interface function for local file."
  (interactive "fFilename: ")
  (w3m (w3m-expand-file-name file)))

(provide 'w3m)
;;; w3m.el ends here.