- (selector (elpher-address-selector address)))
- (elpher-visit-parent-node) ; Do first in case of non-local exits.
- (let* ((filename-proposal (file-name-nondirectory selector))
- (filename (read-file-name "Save file as: "
- nil nil nil
- (if (> (length filename-proposal) 0)
- filename-proposal
- "gopher.file"))))
- (message "Downloading...")
- (setq elpher-download-filename filename)
- (elpher-get-selector address
- (lambda (proc event)
- (let ((coding-system-for-write 'binary))
- (with-temp-file elpher-download-filename
- (insert elpher-selector-string)
- (message (format "Download complate, saved to file %s."
- elpher-download-filename)))))))))
-
-;; URL retrieval
-
-(defun elpher-insert-rendered-html (string)
- "Use shr to insert rendered view of html STRING into current buffer."
- (let ((dom (with-temp-buffer
- (insert string)
- (libxml-parse-html-region (point-min) (point-max)))))
- (shr-insert-document dom)))
-
-(defun elpher-get-url-node ()
- "Getter which attempts to open the URL specified by the current node."
+ (content (elpher-get-cached-content address)))
+ (condition-case the-error
+ (if (and content (funcall renderer nil))
+ (progn
+ (insert content)
+ (elpher-restore-pos))
+ (elpher-with-clean-buffer
+ (insert "LOADING GEMINI... (use 'u' to cancel)"))
+ (elpher-get-gemini address
+ (lambda (proc event)
+ (unless (string-prefix-p "deleted" event)
+ (funcall renderer elpher-gemini-response)
+ (elpher-restore-pos)))))
+ (error
+ (elpher-network-error address the-error)))))
+
+
+(defun elpher-render-gemini (data)
+ "Render gemini response DATA."
+ (if (not data)
+ t
+ (let* ((response-header (car (split-string data "\r\n")))
+ (response-body (substring data (+ (string-match "\r\n" data) 2)))
+ (mime-type-string (string-trim (substring response-header 2)))
+ (mime-type-string* (if (string-empty-p mime-type-string)
+ "text/gemini; charset=utf-8"
+ mime-type-string))
+ (mime-type-split (split-string mime-type-string* ";"))
+ (mime-type (string-trim (car mime-type-split)))
+ (parameters (mapcar (lambda (s)
+ (let ((key-val (split-string s "=")))
+ (list (downcase (string-trim (car key-val)))
+ (downcase (string-trim (cadr key-val))))))
+ (cdr mime-type-split))))
+ (if (and (equal "text/gemini" mime-type)
+ (not (assoc "charset" parameters)))
+ (setq parameters (cons (list "charset" "utf-8") parameters)))
+ (when (string-prefix-p "text/" mime-type)
+ (if (assoc "charset" parameters)
+ (setq elpher-gemini-response
+ (decode-coding-string elpher-gemini-response
+ (intern (cadr (assoc "charset" parameters))))))
+ (setq elpher-gemini-response
+ (replace-regexp-in-string "\r" "" elpher-gemini-response)))
+ (pcase mime-type
+ ((or "text/gemini" "")
+ (elpher-render-gemini-text/gemini response-body parameters))
+ ((pred (string-prefix-p "text/"))
+ (elpher-render-gemini-text/plain response-body parameters))
+ ((pred (string-prefix-p "image/"))
+ (elpher-render-image response-body))
+ (other
+ (error "Unsupported MIME type %S" mime-type))))))
+
+(defun elpher-gemini-get-link-url (line)
+ (string-trim (elt (split-string (substring line 2)) 0)))
+
+(defun elpher-gemini-get-link-display-string (line)
+ (let* ((rest (string-trim (elt (split-string line "=>") 1)))
+ (idx (string-match "[ \t]" rest)))
+ (if idx
+ (string-trim (substring rest (+ idx 1)))
+ "")))
+
+(defun elpher-address-from-gemini-url (url)
+ "Extract address from URL with defaults as per gemini map files."
+ (let ((address (url-generic-parse-url url)))
+ (unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
+ (setf (url-fullness address) t)
+ (unless (url-host address) ;if there is an explicit host, filenames are explicit
+ (setf (url-host address) (url-host (elpher-node-address elpher-current-node)))
+ (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
+ (setf (url-filename address)
+ (concat (file-name-directory
+ (url-filename (elpher-node-address elpher-current-node)))
+ (url-filename address)))))
+ (unless (url-type address)
+ (setf (url-type address) "gemini")))
+ address))
+
+(defun elpher-render-gemini-text/gemini (data parameters)
+ (elpher-with-clean-buffer
+ (dolist (line (split-string data "\n"))
+ (if (string-prefix-p "=>" line)
+ (let* ((url (elpher-gemini-get-link-url line))
+ (display-string (elpher-gemini-get-link-display-string line))
+ (address (elpher-address-from-gemini-url url)))
+ (if (> (length display-string) 0)
+ (elpher-insert-index-record display-string address)
+ (elpher-insert-index-record url address)))
+ (elpher-insert-index-record line)))
+ (elpher-cache-content
+ (elpher-node-address elpher-current-node)
+ (buffer-string))))
+
+(defun elpher-render-gemini-text/plain (data parameters)
+ (elpher-with-clean-buffer
+ (insert (elpher-buttonify-urls (elpher-preprocess-text-response data)))
+ (elpher-cache-content
+ (elpher-node-address elpher-current-node)
+ (buffer-string))))
+
+;; Other URL node opening
+
+(defun elpher-get-other-url-node (renderer)
+ "Getter which attempts to open the URL specified by the current node (RENDERER must be nil)."
+ (when renderer
+ (elpher-visit-parent-node)
+ (error "Command not supported for general URLs"))