-;;; elpher.el --- A friendly gopher client. -*- lexical-binding:t -*-
+;;; elpher.el --- A friendly gopher client -*- lexical-binding:t -*-
;; Copyright (C) 2019 Tim Vaughan
Otherwise, use the system browser via the BROWSE-URL function."
:type '(boolean))
-(defcustom elpher-buttonify-urls-in-directories t
- "If non-nil, turns URLs matched in directories into clickable buttons."
- :type '(boolean))
-
(defcustom elpher-use-header t
"If non-nil, display current page information in buffer header."
:type '(boolean))
"Specifies the number of seconds to wait for a network connection to time out."
:type '(integer))
+(defcustom elpher-strip-ansi-from-text t
+ "If non-nil, strip ANSI escape sequences from gopher menus and text/gemini files.
+This is occasionally desirable, as these sequences are not understood natively by
+Emacs, and tend to result in a garbled display."
+ :type '(boolean))
+
;;; Model
;;
""
(substring (url-filename address) 2)))
-;; Page
-
;; Cache
"Set the cursor position cache for ADDRESS to POS."
(puthash address pos elpher-pos-cache))
+
;; Page
-(defun elpher-make-page (address display-string)
- (list address display-string))
+(defun elpher-make-page (display-string address)
+ (list display-string address))
-(defun elpher-page-address (page)
+(defun elpher-page-display-string (page)
(elt page 0))
-(defun elpher-page-display-string (page)
+(defun elpher-page-address (page)
(elt page 1))
-
(defvar elpher-current-page nil)
(defvar elpher-history nil)
unless NO-HISTORY is non-nil."
(elpher-save-pos)
(elpher-process-cleanup)
- (unless no-history
- (push page elpher-history))
+ (unless (or no-history
+ (equal (elpher-page-address elpher-current-page)
+ (elpher-page-address page)))
+ (push elpher-current-page elpher-history))
(setq elpher-current-page page)
(let* ((address (elpher-page-address page))
(type (elpher-address-type address))
(defun elpher-visit-previous-page ()
"Visit the previous page in the history."
(let ((previous-page (pop elpher-history)))
- (when previous-page
- (elpher-visit-page previous-page nil t))))
+ (if previous-page
+ (elpher-visit-page previous-page nil t)
+ (error "No previous page."))))
(defun elpher-reload-current-page ()
"Reload the current page, discarding any existing cached content."
(elpher-insert-margin)
(let ((propertized-display-string
(propertize display-string 'face 'elpher-info)))
- (insert (if elpher-buttonify-urls-in-directories
- (elpher-buttonify-urls propertized-display-string)
- propertized-display-string))))
+ (insert (elpher-process-text-for-display propertized-display-string))))
(`(gopher ,selector-type) ;; Unknown
(elpher-insert-margin (concat (char-to-string selector-type) "?"))
(insert (propertize display-string
;; Text rendering
(defconst elpher-url-regex
- "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]*[a-zA-Z0-9\-]\\|\[[a-zA-Z0-9:]+\]\\)\\(:[0-9]+\\)?\\(/\\([0-9a-zA-Z\-_~?/@|:.%#]*[0-9a-zA-Z\-_~?/@|#]\\)?\\)?"
- "Regexp used to locate and buttniofy URLs in text files loaded by elpher.")
+ "\\([a-zA-Z]+\\)://\\([a-zA-Z0-9.\-]*[a-zA-Z0-9\-]\\|\[[a-zA-Z0-9:]+\]\\)\\(:[0-9]+\\)?\\(/\\([0-9a-zA-Z\-_~?/@|:.%#=&]*[0-9a-zA-Z\-_~?/@|#]\\)?\\)?"
+ "Regexp used to locate and buttinofy URLs in text files loaded by elpher.")
(defun elpher-buttonify-urls (string)
"Turn substrings which look like urls in STRING into clickable buttons."
'face 'button)))
(buffer-string)))
+(defconst elpher-ansi-regex "\x1b\\[[^m]*m"
+ "Wildly incomplete regexp used to strip out some troublesome ANSI escape sequences.")
+
+(defun elpher-strip-ansi (string)
+ "Strip ANSI escape codes from STRING."
+ (with-temp-buffer
+ (insert string)
+ (goto-char (point-min))
+ (while (re-search-forward elpher-ansi-regex nil t)
+ (delete-region (match-beginning 0) (match-end 0)))
+ (buffer-string)))
+
+
+(defun elpher-process-text-for-display (string)
+ "Perform any desired processing of text prior to display.
+Currently includes buttonifying URLs and optionally stripping ANSI escape codes."
+ (elpher-buttonify-urls (if elpher-strip-ansi-from-text
+ (elpher-strip-ansi string)
+ string)))
+
(defun elpher-render-text (data &optional _mime-type-string)
"Render DATA as text. MIME-TYPE-STRING is unused."
(elpher-with-clean-buffer
(if (not data)
t
- (insert (elpher-buttonify-urls (elpher-preprocess-text-response data)))
+ (insert (elpher-process-text-for-display (elpher-preprocess-text-response data)))
(elpher-cache-content
(elpher-page-address elpher-current-page)
(buffer-string)))))
(defun elpher-render-gemini-plain-text (data _parameters)
"Render DATA as plain text file. PARAMETERS is currently unused."
(elpher-with-clean-buffer
- (insert (elpher-buttonify-urls data))
+ (insert (elpher-process-text-for-display data))
(elpher-cache-content
(elpher-page-address elpher-current-page)
(buffer-string))))
(interactive)
(push-button))
-(defun elpher-go ()
- "Go to a particular gopher site read from the minibuffer."
- (interactive)
- (let ((page
- (let ((host-or-url (read-string "Gopher or Gemini URL: ")))
- (elpher-make-page host-or-url
- (elpher-address-from-url host-or-url)))))
+(defun elpher-go (host-or-url)
+ "Go to a particular gopher site HOST-OR-URL.
+When run interactively HOST-OR-URL is read from the minibuffer."
+ (interactive "sGopher or Gemini URL: ")
+ (let ((page (elpher-make-page host-or-url
+ (elpher-address-from-url host-or-url))))
(switch-to-buffer "*elpher*")
- (elpher-visit-page page)))
+ (elpher-visit-page page)
+ '()))
(defun elpher-go-current ()
"Go to a particular site read from the minibuffer, initialized with the current URL."
(defun elpher-redraw ()
"Redraw current page."
(interactive)
- (if elpher-current-page
- (elpher-visit-page elpher-current-page)
- (message "No current site.")))
+ (elpher-visit-page elpher-current-page))
(defun elpher-reload ()
"Reload current page."
(interactive)
- (if elpher-current-page
- (elpher-reload-current-page)
- (message "No current site.")))
+ (elpher-reload-current-page))
(defun elpher-toggle-tls ()
"Toggle TLS encryption mode for gopher."
(defun elpher-view-raw ()
"View raw server response for current page."
(interactive)
- (if elpher-current-page
- (if (elpher-address-special-p (elpher-page-address elpher-current-page))
- (error "This page was not generated by a server")
- (elpher-visit-page elpher-current-page
- #'elpher-render-raw))
- (message "No current site.")))
+ (if (elpher-address-special-p (elpher-page-address elpher-current-page))
+ (error "This page was not generated by a server")
+ (elpher-visit-page elpher-current-page
+ #'elpher-render-raw)))
(defun elpher-back ()
"Go to previous site."
(interactive)
- (if elpher-history
- (error "No previous site")
- (elpher-visit-previous-page)))
+ (elpher-visit-previous-page))
(defun elpher-download ()
"Download the link at point."