X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=elpher.git;a=blobdiff_plain;f=elpher.el;h=ccba71639b54767e688450c2cf54754065f8f09b;hp=bb1dc61c01904106ed7dee8304200cd3577b4079;hb=bc4fbb5c3361454f9d18e03960c1febcbdcf0b9b;hpb=77b5d72073a6cf8ae293030a7cd4efab3d05a87b diff --git a/elpher.el b/elpher.el index bb1dc61..ccba716 100644 --- a/elpher.el +++ b/elpher.el @@ -20,7 +20,7 @@ ;; Author: Tim Vaughan ;; Created: 11 April 2019 -;; Version: 3.1.0 +;; Version: 3.2.1 ;; Keywords: comm gopher ;; Homepage: https://thelambdalab.xyz/elpher ;; Package-Requires: ((emacs "27.1")) @@ -85,7 +85,7 @@ ;;; Global constants ;; -(defconst elpher-version "3.1.0" +(defconst elpher-version "3.2.1" "Current version of elpher.") (defconst elpher-margin-width 6 @@ -227,8 +227,14 @@ page within which all of the standard elpher keybindings are active." (defcustom elpher-start-page "about:welcome" "Specify the page displayed initially by elpher. -The default welcome screen \"about:welcome\", while the bookmarks list -is \"about:bookmarks\". You can also specify local files via \"file:\".") +The default welcome screen is \"about:welcome\", while the bookmarks list +is \"about:bookmarks\". You can also specify local files via \"file:\". + +Beware that using \"about:bookmarks\" as a start page in combination with +the `elpher-use-bookmark-menu' variable set to non-nil will prevent the +Emacs bookmark menu being accessible via \\[elpher-show-bookmarks] from +the start page." + :type '(string)) ;; Face customizations @@ -396,24 +402,23 @@ requiring gopher-over-TLS." "Retrieve type of ADDRESS object. This is used to determine how to retrieve and render the document the address refers to, via the table `elpher-type-map'." - (let ((protocol (url-type address))) - (pcase (url-type address) - ("about" - (list 'about (intern (url-filename address)))) - ((or "gopher" "gophers") - (list 'gopher - (if (member (url-filename address) '("" "/")) - ?1 - (string-to-char (substring (url-filename address) 1))))) - ("gemini" 'gemini) - ("telnet" 'telnet) - ("finger" 'finger) - ("file" 'file) - (_ 'other-url)))) + (pcase (url-type address) + ("about" + (list 'about (intern (url-filename address)))) + ((or "gopher" "gophers") + (list 'gopher + (if (member (url-filename address) '("" "/")) + ?1 + (string-to-char (substring (url-filename address) 1))))) + ("gemini" 'gemini) + ("telnet" 'telnet) + ("finger" 'finger) + ("file" 'file) + (_ 'other-url))) (defun elpher-address-about-p (address) "Return non-nil if ADDRESS is an about address." - (pcase (elpher-address-type address) (`(about ,subtype) t))) + (pcase (elpher-address-type address) (`(about ,_) t))) (defun elpher-address-gopher-p (address) "Return non-nill if ADDRESS object is a gopher address." @@ -512,8 +517,9 @@ previously-visited pages,unless NO-HISTORY is non-nil." (elpher-save-pos) (elpher-process-cleanup) (unless no-history - (unless (equal (elpher-page-address elpher-current-page) - (elpher-page-address page)) + (unless (or (not elpher-current-page) + (equal (elpher-page-address elpher-current-page) + (elpher-page-address page))) (push elpher-current-page elpher-history) (unless (or (elpher-address-about-p (elpher-page-address page)) (and elpher-visited-pages @@ -539,10 +545,9 @@ previously-visited pages,unless NO-HISTORY is non-nil." (defun elpher-visit-previous-page () "Visit the previous page in the history." - (let ((previous-page (pop elpher-history))) - (if previous-page - (elpher-visit-page previous-page nil t) - (error "No previous page")))) + (if elpher-history + (elpher-visit-page (pop elpher-history) nil t) + (error "No previous page"))) (defun elpher-reload-current-page () "Reload the current page, discarding any existing cached content." @@ -768,7 +773,7 @@ the host operating system and the local network capabilities.)" (elpher-process-cleanup) (cond ; Try again with IPv4 - ((not (or force-ipv4 socks)) + ((not (or elpher-ipv4-always force-ipv4 socks)) (message "Connection timed out. Retrying with IPv4.") (elpher-get-host-response address default-port query-string @@ -789,7 +794,9 @@ the host operating system and the local network capabilities.)" (proc (if socks (socks-open-network-stream "elpher-process" nil host service) (make-network-process :name "elpher-process" :host host - :family (and force-ipv4 'ipv4) + :family (and (or force-ipv4 + elpher-ipv4-always) + 'ipv4) :service service :buffer nil :nowait t @@ -1017,25 +1024,6 @@ once they are retrieved from the gopher server." ;; Index rendering -(defun elpher-insert-index (string) - "Insert the index corresponding to STRING into the current buffer." - ;; Should be able to split directly on CRLF, but some non-conformant - ;; LF-only servers sadly exist, hence the following. - (let ((str-processed (elpher-preprocess-text-response string))) - (dolist (line (split-string str-processed "\n")) - (ignore-errors - (unless (= (length line) 0) - (let* ((type (elt line 0)) - (fields (split-string (substring line 1) "\t")) - (display-string (elt fields 0)) - (selector (elt fields 1)) - (host (elt fields 2)) - (port (if (elt fields 3) - (string-to-number (elt fields 3)) - nil)) - (address (elpher-make-gopher-address type selector host port))) - (elpher-insert-index-record display-string address))))))) - (defun elpher-insert-margin (&optional type-name) "Insert index margin, optionally containing the TYPE-NAME, into the current buffer." (if type-name @@ -1103,7 +1091,20 @@ If ADDRESS is not supplied or nil the record is rendered as an (elpher-with-clean-buffer (if (not data) t - (elpher-insert-index data) + (let ((data-processed (elpher-preprocess-text-response data))) + (dolist (line (split-string data-processed "\n")) + (ignore-errors + (unless (= (length line) 0) + (let* ((type (elt line 0)) + (fields (split-string (substring line 1) "\t")) + (display-string (elt fields 0)) + (selector (elt fields 1)) + (host (elt fields 2)) + (port (if (elt fields 3) + (string-to-number (elt fields 3)) + nil)) + (address (elpher-make-gopher-address type selector host port))) + (elpher-insert-index-record display-string address)))))) (elpher-cache-content (elpher-page-address elpher-current-page) (buffer-string))))) @@ -1131,8 +1132,8 @@ If ADDRESS is not supplied or nil the record is rendered as an nil t)) (window (get-buffer-window elpher-buffer-name))) (when window - (setf (image-property image :max-width) (window-pixel-width window)) - (setf (image-property image :max-height) (window-pixel-height window))) + (setf (image-property image :max-width) (window-body-width window t)) + (setf (image-property image :max-height) (window-body-height window t))) (elpher-with-clean-buffer (insert-image image) (elpher-restore-pos))) @@ -1512,28 +1513,36 @@ by HEADER-LINE." "Insert a plain non-preformatted TEXT-LINE into a text/gemini document. This function uses Emacs' auto-fill to wrap text sensibly to a maximum width defined by `elpher-gemini-max-fill-width'." - (string-match "\\(^[ \t]*\\)\\(\\*[ \t]+\\|>[ \t]*\\)?" text-line) - (let* ((line-prefix (match-string 2 text-line)) - (processed-text-line - (if line-prefix - (cond ((string-prefix-p "*" line-prefix) - (concat - (replace-regexp-in-string "\\*" - elpher-gemini-bullet-string - (match-string 0 text-line)) - (substring text-line (match-end 0)))) - ((string-prefix-p ">" line-prefix) - (propertize text-line 'face 'elpher-gemini-quoted)) - (t text-line)) - text-line)) - (adaptive-fill-mode t) - ;; fill-prefix is important for adaptive-fill-mode: without - ;; it, multi-line list items are not indented correct - (fill-prefix (if (match-string 2 text-line) - (replace-regexp-in-string "[>\*]" " " (match-string 0 text-line)) - nil))) - (insert (elpher-process-text-for-display processed-text-line)) - (newline))) + (if (string-empty-p text-line) + (insert "\n") + (string-match + (rx (: line-start + (* (any " \t")) + (optional + (group (or (: "*" (+ (any " \t"))) + (: ">" (* (any " \t")))))))) + text-line) + (let* ((line-prefix (match-string 1 text-line)) + (processed-text-line + (if line-prefix + (cond ((string-prefix-p "*" line-prefix) + (concat + (replace-regexp-in-string "\\*" + elpher-gemini-bullet-string + (match-string 0 text-line)) + (substring text-line (match-end 0)))) + ((string-prefix-p ">" line-prefix) + (propertize text-line 'face 'elpher-gemini-quoted)) + (t text-line)) + text-line)) + (adaptive-fill-mode t) + ;; fill-prefix is important for adaptive-fill-mode: without + ;; it, multi-line list items are not indented correct + (fill-prefix (if (match-string 1 text-line) + (make-string (length (match-string 0 text-line)) ?\s) + nil))) + (insert (elpher-process-text-for-display processed-text-line)) + (newline)))) (defun elpher-render-gemini-map (data _parameters) "Render DATA as a gemini map file, PARAMETERS is currently unused." @@ -1626,7 +1635,7 @@ The result is rendered using RENDERER." ;; File page (defun elpher-get-file-page (renderer) - "Getter which retrieves the contents of a local file and renders it using RENDERER. + "Getter which renders a local file using RENDERER. Assumes UTF-8 encoding for all text files." (let* ((address (elpher-page-address elpher-current-page)) (filename (elpher-address-filename address))) @@ -1640,7 +1649,7 @@ Assumes UTF-8 encoding for all text files." (let ((coding-system-for-read 'binary) (coding-system-for-write 'binary)) (insert-file-contents-literally filename) - (string-as-unibyte (buffer-string)))))) + (encode-coding-string (buffer-string) 'raw-text))))) (if renderer (funcall renderer body nil) (pcase (file-name-extension filename) @@ -1824,20 +1833,22 @@ If `elpher-bookmark-link' is non-nil and point is on a link button, return a bookmark record for that link. Otherwise, return a bookmark record for the current elpher page." (let* ((button (and elpher-bookmark-link (button-at (point)))) - (page (if button - (button-get button 'elpher-page) - elpher-current-page)) - (address (elpher-page-address page)) - (url (elpher-address-to-url address)) - (display-string (elpher-page-display-string page)) - (pos (if button nil (point)))) - (if (elpher-address-about-p address) - (error "Cannot bookmark %s" display-string) - `(,display-string - (defaults . (,display-string)) - (position . ,pos) - (location . ,url) - (handler . elpher-bookmark-jump))))) + (page (if button + (button-get button 'elpher-page) + elpher-current-page))) + (unless page + (error "Cannot bookmark this link")) + (let* ((address (elpher-page-address page)) + (url (elpher-address-to-url address)) + (display-string (elpher-page-display-string page)) + (pos (if button nil (point)))) + (if (elpher-address-about-p address) + (error "Cannot bookmark %s" display-string) + `(,display-string + (defaults . (,display-string)) + (position . ,pos) + (location . ,url) + (handler . elpher-bookmark-jump)))))) ;;;###autoload (defun elpher-bookmark-jump (bookmark) @@ -1903,7 +1914,8 @@ To bookmark the link at point use \\[elpher-bookmark-link]." (elpher-bookmark-import old-bookmarks-file) (rename-file old-bookmarks-file (concat old-bookmarks-file "-legacy")))) - (if elpher-use-emacs-bookmark-menu + (if (and elpher-use-emacs-bookmark-menu + elpher-history) (progn (elpher-visit-previous-page) (call-interactively #'bookmark-bmenu-list)) @@ -2205,9 +2217,8 @@ When run interactively HOST-OR-URL is read from the minibuffer." (error "Command invalid for %s" (elpher-page-display-string elpher-current-page))))) (defun elpher-info-page (page) - "Display information on PAGE." - (let ((display-string (elpher-page-display-string page)) - (address (elpher-page-address page))) + "Display URL of PAGE in minibuffer." + (let ((address (elpher-page-address page))) (message "%s" (elpher-address-to-url address)))) (defun elpher-info-link ()