[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: nero.el

From: Joe Corneli
Subject: Re: nero.el
Date: Thu, 24 Mar 2005 21:05:44 -0600

Here's a new version of nero.el that I *know* you've all been waiting

Link to development version is as in last posting.

* Comparison with previous release:

** Added elvi (you can search wikipedia, etc.)

** optional decoding of some unicode strings

** preview html found in your own strings and buffers

** process images and other assorted file types

** mode behavior is better now

* took a stab at "tabbed browsing"; not finished yet.

Nero is now my "default browser" ;)... 

;;; nero.el --- a fast Lynx-based browser for Emacs

;; Copyright (C) 2005 Joe Corneli <address@hidden>

;; Time-stamp: <jac -- Thu Mar 24 20:50:56 CST 2005>

;; This file is not part of GNU Emacs, but it is distributed under
;; the same terms as GNU Emacs.

;; GNU Emacs 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, or (at your
;; option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.

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

;;; Commentary:

;; Nero is a fast interactive web browser that runs under Emacs.  It
;; uses 'lynx -dump' to generate page content.  Navigation is
;; typically done via numbered links, a la Conkeror.  See
;; `nero-follow-link'.  Note that cookies that have been set by Lynx
;; continue to apply to Nero.  Nero itself does not set cookies.

;; Better-than-just-browsing interaction with various useful web
;; services is done via elvi, a la Surfraw.  The variable `nero-elvi'
;; contains a list of the currently available elvi.  Contributions of
;; new elvi are welcomed by the author.  See `nero-defelvis' for
;; instructions on how to write your own elvi.

;; Facilities for "tabbed browsing" in nero are in development.

;; Example usage:
;; M-x nero-browse-url RET RET
;; 7 RET
;; b
;; C-s otho C-a RET
;; M-x nero-query-wikipedia RET fiddle burn RET
;; M-x nero-bookmark-current-url RET
;; M-x nero-browse-url RET RET
;; M-x nero-preview-buffer RET index.html RET
;; ...

;; Features to add:

;; * finish tabbed browsing implementation
;; * make sure that buffer names are actually handled
;;   properly as part of the grand scheme of things.
;; * Before opening a page, check to see whether we processed 
;;   that URL before.
;; * search through all pages in the history/future for terms
;;   that we might find interesting! (Similarly across timescapes.)
;; * deal with images and other weird formats appropriately.
;;   - offer to (c)ontinue, (u)se external program, 
;;     just (d)ownload, or [for images] (s)witch to 
;;     image mode and open.
;; * integration with external/other browsers
;; * fix nasty googlification (see `ungooglify-url').
;; * improve parsing/display for UTF-8 characters
;; * add display of special characters from GCIDE (and function
;;   for querying GCIDE :)).
;; * it might be cool at some point to build a graph of the URL's
;;   that have been browsed to

;;; Code:

;; opulence
(require 'cl)

(defvar nero-old-layout nil "Browser windows are currently all
fullscreen.  This variable saves the window layout that was
active before browsing started.  The old layout is restored when
either `nero-hide' or `nero-finished' runs.")

(defvar nero-history nil "Record of pages already visited.
See also `nero-future', `nero-ariadnes-thread'.")

(defvar nero-future nil
  "Record of pages visited and then retreated from with `nero-back'.
See also `nero-history', `nero-ariadnes-thread'.")

(defvar nero-moment nil
  "A distinguished moment in time.
The user can declare any page to be `nero-moment'.  See
`nero-mark-page' and `nero-goto-mark'.")

(defvar nero-moments nil
  "A list of distinguished moments.
Every moment set by `nero-mark-page' is added to this

(defvar nero-timescape nil
  "A distinguished history and future.
The user can use this variable to save the state of
`nero-history' and `nero-future'.  See `nero-set-timescape'
and `nero-get-timescape'.")

(defvar nero-timescapes nil
  "A list of timescapes.
Every timescape set by `nero-set-timescape' is added to this

(defvar nero-tab nil
  "The current tab.
See `nero-set-tab' and `nero-get-tab'.")

(defvar nero-tabs nil
  "A list of tabs.
Every tab set by `nero-set-tab' is added to this list.")

(defvar nero-ariadnes-thread nil "Full record of the pages visited.
Every page loaded by `nero-browse-url' is added to this list.
Even more extensive recording is done if `nero-long-thread' is

(defvar nero-long-thread nil
  "*Should `nero-ariadnes-thread' record effects of `nero-restore-page'?")

(defvar nero-mode-hook nil
  "*Functions to run when `nero-mode' runs.")

(defvar nero-before-browsing-hook nil
  "*Functions to run when `nero-browse-url' runs.
This can be used to associate certain actions with opening a
link. (If it begins to seem useful, we'll add an \"after
browsing\" hook too.)")

(defvar nero-fullscreen t
  "*Whether or not starting nero should delete other windows.")

(defvar nero-images nil
  "*Whether or not nero should display links for images.")

(defvar nero-homepage "";
  "*Variable for user homepage.")

(defvar nero-bookmarks "~/.lynx_bookmarks.html"
  "*Variable for user bookmarks.")

(defvar nero-links-display t 
  "*Whether or not links appear in the buffer.
Set with `nero-toggle-links-display'.")

(defvar nero-links-exist t
  "*Whether or not there are links in the buffer.
Set with `nero-toggle-links-exist'.")

(defvar nero-links-sanctuary nil
  "Save links here when deleting with `nero-toggle-links-exist'.")

(defvar nero-extensions-and-instructions '(("jpg" . image) 
                                           ("png" . image)
                                           ("gif" . image)
                                           ("ps" . download)
                                           ("pdf" . download))
  "File extensions and instructions for nero on how to handle
them This is an alist; the car of each element is a string giving
the file extension, and the cdr is one of several special handler
descriptions, or the name of a function to call.")

(defvar nero-decode-unicode t
  "Whether or not to decode unicode strings (like \"&#29572;\").")

(defvar nero-elvi nil
  "List of elvi known to nero.
Adjusted whenever `nero-defelvis' adds a new elvis.")

;; this could be switched by `nero-mode' to be buffer local if
;; transition to allowing multiple `nero-mode' windows at once.
(defvar nero-buffer-in-nero-mode nil 
  "Says whether or not the *Nero* buffer is in `nero-mode'.
This variable is investigated every time `nero-browse-url' runs.
If we're not in the right mode, `nero-browse-url' runs `nero-mode'
and sets this variable to t.  Whenever the *Nero* buffer is killed,
the variable is set back to nil.")

(defvar nero-mode-map
    (let ((map (make-keymap)))
      (suppress-keymap map)
      (define-key map "a" 'nero-bookmark-current-url)
      (define-key map "A" 'nero-bookmark-current-link)
      (define-key map "b" 'nero-back)
      (define-key map "B" 'nero-back-to-beginning)
      (define-key map "c" 'nero-get-timescape)
      (define-key map "C" 'nero-set-timescape)
      (define-key map "d" 'nero-download-link)
      (define-key map "f" 'nero-forward)
      (define-key map "F" 'nero-forward-to-end)
      (define-key map "g" 'nero-browse-url)
      (define-key map "h" 'nero-home)
      (define-key map "i" 'nero-new-tab)
      (define-key map "I" 'nero-set-tab)
      (define-key map "m" 'nero-goto-mark)
      (define-key map "M" 'nero-mark-page)
      (define-key map "n" 'nero-return-to-nero-now)
      (define-key map "q" 'nero-finished)
      (define-key map "r" 'nero-reload)
      (define-key map "s" 'nero-show-current-timescape)
      (define-key map "S" 'nero-show-ariadnes-thread)
      (define-key map "t" 'nero-toggle-links-display)
      (define-key map "T" 'nero-toggle-links-exist)
      (define-key map "u" 'nero-kill-ring-save-current-url)
      (define-key map "U" 'nero-kill-ring-save-current-link)
      (define-key map "v" 'nero-bookmarks)
      (define-key map "w" 'nero-show-tabs)
      (define-key map ";" 'nero-hide)
      (define-key map "'" 'nero-tabula-rosa)
      (define-key map "?" 'describe-mode)
      (define-key map (kbd "RET") 'nero-follow-link)
      (define-key map (kbd "TAB") 'nero-move-to-next-link)
      (define-key map (kbd "SPC") 'scroll-up)
      (define-key map (kbd "DEL") 'scroll-down)
  "Keymap for nero major mode.")

(defvar nero-mode-syntax-table
  (let ((nero-mode-syntax-table text-mode-syntax-table))

(defvar nero-link-regexp "\\[\\([0-9]+\\)\\]"
  "Regular expression that tells nero what links look like.
The first parenthesized subexpression is the unique string
denoting the webpage to load, which will sought among the

(defvar nero-font-lock-keywords
    (list `(,nero-link-regexp . font-lock-keyword-face)))
  "Font lock for `nero-mode'.
Currently, only numbered links are fontified.")

(defun nero-mode ()
  "Major mode for browsing the web.
Entry to this mode calls the value of `nero-mode-hook'
if that value is non-nil."
  (set-syntax-table nero-mode-syntax-table)
  (use-local-map nero-mode-map)
  (setq major-mode 'nero-mode)
  (set (make-local-variable 'font-lock-defaults)
  (set (make-local-variable 'nero-buffer-in-nero-mode) t)
  (setq major-mode 'nero-mode)
  (setq mode-name "nero")
  ;; OK, this is sort of silly, but at least we only call it when
  ;; `nero-mode' runs!
  (when nero-fullscreen
  (add-hook 'kill-buffer-hook 'nero-wind-down nil t)
  ;; run hook before beginning
  (run-hooks 'nero-mode-hook))

;; useful for debugging, etc.
;; we should distinguish between the commands that wipe out everything
;; and the commands that just wipe out a certain timescape.
(defun nero-tabula-rosa ()
  "Reinitialize nero's history and future."
  (setq nero-history nil
        nero-future nil))

(defun nero-scorched-earth ()
  "Set each of nero's state variables to nil except `nero-history'.
This is set to the car of the current `nero-history'."
  (setq nero-history (list (car nero-history))
        nero-future nil)
  (setq nero-old-layout nil
        nero-ariadnes-thread nil
        nero-tabs nil
        nero-moments nil
        nero-timescapes nil
        nero-tab nil
        nero-moment nil
        nero-timescape nil))

;; Couldn't get this to work the way I wanted it to.
; (defadvice shell-command-sentinel (around be-quiet activate)
;   "Make the dang thing shut up.")
; (ad-enable-advice 'shell-command-sentinel 'around 'be-quiet)
; (ad-activate 'shell-command-sentinel)
(defun nero-download-url (URL)
  "URL is a web address or path to a file.
Running this command downloads the URL and saves it to a file.
If you want to check on the progress of the download, have a
look at the *Async Shell Command* buffer."
  (let* ((filename (concat (replace-regexp-in-string ".*/" "" URL)))
         (path (read-string "Save as: "
                             (if (equal filename "")
         (doit (concat "wget -O " path " \"" URL "\"&")))
    (let ((winconf (current-window-configuration)))
      (shell-command doit nil nil)
      (set-window-configuration winconf))))

;; the beginnings of a system that can be used to browse the GCIDE w/i
;; Emacs.
(defun nero-browse-url-force-html (URL)
  (interactive "MURL: ")
  (nero-browse-url URL "-force-html"))

(defun nero-preview-region (&optional flags)
  "Preview the current region in nero.
If given, FLAGS is a string giving any additional flags to be
passed to the 'lynx -dump' rendering engine."
  (nero-preview-string (buffer-substring-no-properties

(defun nero-preview-string (str &optional flags)
  "Preview string STR in nero.
If given, FLAGS is a string giving any additional flags to be
passed to the 'lynx -dump' rendering engine."
    (insert str)
    (nero-preview-current-buffer flags)))

(defun nero-preview-buffer (&optional buffer flags)
  "Preview BUFFER (by default, the current buffer) in nero.
If given, FLAGS is a string giving any additional flags to be
passed to the rendering engine. See also `nero-preview-string',
`nero-preview-region', and `nero-preview-current-buffer'."
  (interactive (list (read-string (concat "Buffer "
                                          "(default "
                                          (buffer-name (current-buffer))
                                          "): ")
                                  (buffer-name (current-buffer)))))
  (set-buffer (get-buffer-create buffer))
  (nero-browse-url (buffer-name) flags (cons 'buffer (buffer-name))))

(defun nero-preview-current-buffer (&optional flags)
  "Like `nero-preview-buffer' but automatically uses the current buffer."
  (nero-browse-url (buffer-name) flags (cons 'buffer (buffer-name))))

;; should -probably- definitely set things up so that several nero
;; windows can be used at once... but might as well wait and
;; do that port all at once at some point when the other
;; features stabilize...
;; Note that while there are no references in a buffer with no links,
;; which means that  *Nero Refs" won't be created if the first
;; page we browse has no links.  We set up helper functions so
;; that they don't assume that this buffer has been created.
(defun nero-browse-url (URL &optional flags handler)
  "URL is a web address or path to a file.
Running this command displays a rendered version of the URL in
the *Nero* buffer.  If given, FLAGS is a string giving any
additional flags to be passed to the 'lynx -dump' rendering
  (interactive "MURL: ")
  (run-hooks 'nero-before-browsing-hook)
  ;; set up environment
  (unless nero-old-layout
    (setq nero-old-layout (current-window-configuration)))
  (switch-to-buffer (get-buffer-create "*Nero*"))
  (unless nero-buffer-in-nero-mode
  (let ((curpoint (point)))
    (unless (nero-process-nontext-file URL)
      ;; we should save the current location of the point to the history
      ;; for this page -- but it is important to get straight which of
      ;; the pages histories should be affected...
      ;; generate the content according to the specified handler.
      (nero-generate-page-content URL flags handler)
    (nero-update-timescape URL curpoint))
  ;; I must be missing at least one `save-excursion', otherwise I
  ;; wouldn't have to run `set-buffer' here.
  (set-buffer "*Nero*")
  (goto-char (point-min)))

(defun nero-process-nontext-file (URL)
  "Called by `nero-browse-url' to handle some non-text files specially."
  (let* ((filename (replace-regexp-in-string ".*/" "" URL))
         (extension (replace-regexp-in-string ".*\\." "" filename))
         (handler (cdr (assoc extension 
    (case handler
       (set-buffer (get-buffer-create "*Nero*"))
           ;; thanks Miles Bader
           (set-buffer-multibyte nil)
           (if (file-regular-p URL)
               (insert-file-contents URL)
             (url-insert-file-contents URL))
         nil t)))
       (nero-download-url URL))
      ((functionp handler)
       ;; the handler itself is a function to call
       (funcall handler URL))
      ;; everything else we process as a text file.
      (t nil))))

;; Not called, because we assume Emacs can figure this stuff out on its own.
; `,(nero-type-from-extension extension)
(defun nero-type-from-extension (extension)
  "Return the image type identifier associated with a file extension."
  (cdr (assoc extension '(("jpeg" . jpeg)
                          ("jpg" . jpeg)
                          ("xpm" . xpm)
                          ("xbm" . xbm)
                          ("gif" . gif)
                          ("eps" . postscript)
                          ("pbm" . pbm)
                          ("png" . png)
                          ("tiff" . tiff)))))

;; thanks again Miles Bader! - and note, this (found in the emacs/w3m
;; package) also seems to work.
; (defun w3m-ucs-to-char (codepoint)
;   (or (decode-char 'ucs codepoint) ?~))
; (while (re-search-forward  nil t)
;   (setq ucs (string-to-number (match-string 1)))
;   (delete-region (match-beginning 0) (match-end 0))
;   (insert-char (w3m-ucs-to-char ucs) 1))
(defun nero-translate-unicode-codepoints-to-utf-16 ()
  "Do replacements to decode and render unicode codepoint strings."
  (goto-char (point-min))
  ;; this regexp only deals with a subset of the strings that use
  ;; thing &#.....; syntax.
  (while (re-search-forward "&#\\([0-9]+\\);" nil t)
    (let* ((ucs (string-to-number (match-string 1)))
           (ucs-string (string (logand ucs #xFF) (logand (ash ucs -8) #xFF)))
           (decoded-string (decode-coding-string ucs-string 'mule-utf-16le)))
      (delete-region (match-beginning 0) (match-end 0))
      (insert decoded-string))))

;; Note: we don't make BUFFER default to the current buffer because it
;; is better to be consistent with style of `shell-command-on-region'.
(defun shell-command-on-buffer 
  (buffer command &optional output-buffer replace error-buffer 
  "COMMAND is the shell command to run on the contents of BUFFER.
See `shell-command-on-region' for further details."
  (set-buffer (get-buffer-create buffer))
  (shell-command-on-region (point-min) (point-max) command
                           output-buffer replace error-buffer))

;; Could use this instead of `shell-command-on-buffer' and save having
;; to start the tcsh (thanks David Hunter):
; (call-process-region (point-min) (point-max) "lynx -dump -stdin" nil
;                     (get-buffer-create "*foo*"))
;; Indeed it seems to me that it might be better to use something
;; sorta like this everywhere we use lynx - i.e., skip the tcsh every
;; time.
(defun nero-generate-page-content (URL flags handler)
  "Called by `nero-browse-url' to put initial text in the *Nero* buffer."
   ((eq (car handler) 'buffer)
     (cdr handler)
     (concat "lynx -dump -stdin " 
             (if nero-images "" "-image_links ")
             (if flags (concat " " flags) ""))
    (insert (shell-command-to-string
             (concat "lynx -dump "
                     (if nero-images "" "-image_links")
                     (if flags (concat " " flags) "") 
                     " \""
                     URL "\"")))))
  (when nero-decode-unicode

(defun nero-generate-references ()
  "Called by `nero-browse-url' to set up the  *Nero Refs* buffer."
    (set-buffer "*Nero*")
    (goto-char (point-max))
    (when (search-backward-regexp "^References" nil t)
      (let ((nero-references (buffer-substring-no-properties 
                              (match-beginning 0) (point-max))))
        (delete-region (match-beginning 0) (point-max))
          (set-buffer (get-buffer-create " *Nero Refs*"))
          (delete-region (point-min) (point-max))
          (insert nero-references))))))

(defun nero-update-timescape (URL curpoint)
  "Called by `nero-browse-url' to set history and future, etc."
  ;; update appropriate history cells
  (unless (equal (caar nero-history) URL)
    (setq nero-history 
          (cons (list 
                   (set-buffer (get-buffer-create "*Nero*")) 
                   (set-buffer (get-buffer-create " *Nero Refs*")) 
  (when (second nero-history)
    (setcar (nthcdr 3 (second nero-history)) curpoint))
  ;; update future as needed
  (if (equal (caar nero-future) URL)
      (setq nero-future (cdr nero-future))
    (setq nero-future nil))
  ;; update our ariadne's thread
  (setq nero-ariadnes-thread 
        (cons (caar nero-history) nero-ariadnes-thread))
  ;; set up a a default tab if there are no tabs yet
  (unless nero-tabs
    (nero-set-tab "Tab 1")))

;; the analogy noted in the docstring here should be exploited.
(defun nero-mark-page ()
  "Use this function to set `nero-moment'.
Use `nero-goto-mark' to retrieve to this \"moment\" (see
its docstring for details).  Note that `set-mark', a familiar
editing command, is in some ways analogous to this command."
  (message (concat (caar nero-history) " marked."))
  (setq nero-moment (list
                     (caar nero-history)
                     (save-excursion (set-buffer "*Nero*")
                     (save-excursion (set-buffer " *Nero Refs*")
                     (save-excursion (set-buffer "*Nero*")
        nero-moments (add-to-list 'nero-moments nero-moment)))

;; Probably this should rocket you to whatever tab/timescape the
;; moment is located in.
(defun nero-goto-mark ()
  "Use this function to retreive `nero-moment'.
If `nero-moment' has not been set, this function behaves like
`nero-return-to-nero-now'. Warning: Either the past or the future
may be different from how they were when you ran
`nero-mark-page' (and in fact they probably will be).  See
`nero-set-timescape' and `nero-get-timescape' for a way to set
and restore an entire \"timescape\"."
  (nero-restore-page (or nero-moment
                         (car nero-history))))

(defun nero-goto-mark-no-mark ()
  "Like `nero-goto-mark' but does not mark the current page."
  (nero-restore-page (or nero-moment
                         (car nero-history))))

(defun nero-return-to-nero-now ()
  "Use this function to return to the \"present\".
This is useful to return from the displays produced by
`nero-show-tabs' and `nero-show-ariadnes-thread', both of which
are considered to be \"outside of time\"."
  (nero-restore-page (car nero-history)))

;; it would be VERY good to set up the list structures so that the
;; current link for each timescape could change, but the name would not
;; change.  (But we could, I guess, offer a function to rename a
;; timescape... though I don't know how many people would actually care
;; about that.)
;; Getting the list structures right is going to be the top priority
;; for the next little bit of development!
;; I wonder if it wouldn't be a good idea to set things up so that the
;; user is automatically in some particular timescape at the get-go.
(defun nero-set-timescape (name)
  "Store `nero-history' and `nero-future' in `nero-timescape'.
This timescape can be restored using `nero-get-timescape'.
NAME is the distinguishing name of this timescape."
  (interactive (list (read-string
                      (concat "Name (default: "
                              "): ")
  (let (history future)
    (dolist (moment nero-history)
      (setq history (add-to-list 'history moment t)))
    (dolist (moment nero-future)
      (setq future (add-to-list 'future moment t)))
    (setq nero-timescape (list history future)))
  ;; They are given user-defined names... maybe force uniqueness?
  (setq nero-timescapes 
        (add-to-list 'nero-timescapes 
                     (list name
                           (caar nero-history) 

(defun nero-default-new-timescape-name ()
  "Generate the default name of a new timescape."
  (concat "Timescape " 
           (1+ (length nero-timescapes)))))

;; this should be adjusted to restore *any* timescape that has
;; been saved.  (And the variable `nero-timescape' should
;; just hold the name of the current one.)
(defun nero-get-timescape ()
  "Restore values stored in `nero-timescape'.
This sets `nero-history' and `nero-future'."
    (setq nero-history (car nero-timescape)
          nero-future (cdr nero-timescape))
    (nero-restore-page (car nero-history)))
    (message "You have not yet saved any timescapes."))))

;; this and the next two things are a fairly roundabout way of doing
;; what we're trying to do - they were written at a time when
;; `nero-preview-buffer' wasn't working.  Probably this is not the
;; best way to do things - but oh well, it works.
(defun nero-show-current-timescape ()
  "Show browsing history and future, indicating current page.
The page is navigable, or you can use `nero-return-to-nero-now'
to return to browsing the current page."
  (set-buffer " *Nero Refs*")
  (let ((number 1)
        (hist nero-history)
        (fut nero-future))
    (while fut
      (insert (concat " " (int-to-string number) ". " (caar fut) "\n"))
      (setq fut (cdr fut))
      (setq number (1+ number)))
    ;; mark the current page
    (insert (concat " " (int-to-string number) ". " (caar hist) " ***\n"))
    (setq hist (cdr hist))
    (setq number (1+ number))
    (while hist
      (insert (concat " " (int-to-string number) ". " (caar hist) "\n"))
      (setq hist (cdr hist))
      (setq number (1+ number))))

(defun nero-show-ariadnes-thread ()
  "Show a page displaying a full record of the pages that have loaded.
See `nero-ariadnes-thread'."
  (set-buffer " *Nero Refs*")
  (let ((number 1)
        (thread nero-ariadnes-thread))
    (while thread
      (insert (concat " " (int-to-string number) ". " (car thread) "\n"))
      (setq thread (cdr thread))
      (setq number (1+ number))))

(defun nero-show-tabs ()
  "Switch to the *Nero Tabs* buffer, a marked-up list of tabs.
Following a link in this buffer makes the linked tab
current. (This is accomplished by adding `nero-restore-tab'
attached to the local value of `nero-follow-link-hook' in the
*Nero Tabs* buffer.)"
    (set-buffer (get-buffer-create " *Nero Refs*"))
    (let ((number 1)
          (tabs nero-tabs))
      (while tabs
         (concat " " (int-to-string number) 
                 ". " (caar (second (car tabs)))
                 " " (first (car tabs)) "\n"))
        (setq tabs (cdr tabs))
        (setq number (1+ number))))
    (nero-mark-up-references-for-browsing "*Nero Tabs*")
    (set-buffer (get-buffer "*Nero Tabs*"))
    (add-hook 'nero-follow-link-hook 'nero-restore-tab nil t)
    (bury-buffer "*Nero Tabs*"))
   (t (nero-set-tab "Tab 1")

(defun nero-mark-up-references-for-browsing (&optional buffer)
  "Turns a numbered list of references into something browsable.
Called by various functions, including `nero-show-tabs' and
  (let ((content (buffer-string)))
    (switch-to-buffer (get-buffer-create (or buffer
    ;; if we create a new buffer, we have to switch it to *Nero Mode*
    ;; -- actually, we might want to use some other derived modes
    ;; for doing dired-like actions on various lists of things,
    ;; but that can wait a bit
    (unless nero-buffer-in-nero-mode
    (insert "\n")
    (insert content)
    (goto-char (point-min)))
  (while (re-search-forward "^ \\([0-9]+\\)\. \\([^ \n]*\\)\\(.*\\)" nil t)
    (let* ((num (match-string 1))
           (link (match-string 2))
           (trailing (match-string 3))
           (beg (match-beginning 0))
           (end (match-end 0))
             ((equal trailing " ***")
              (concat "** [" num "] "  link ))
             ((not (equal trailing ""))
              (concat "   [" num "] \"" 
                      ;; it is annoying to have to do this, but I
                      ;; couldn't figure out how to accomplish it all
                      ;; with the one `re-search-forward'.
                       "^ +" "" trailing)
                      "\" viewing "  link ""))
              (concat "   [" num "] " link)))))
      (delete-region beg end)
      (insert replacement)))
  (goto-char (point-min)))

;; this either browses the same page; we could write a function that
;; creates a new tab, that browsing another page (open in new tab).
;; we could also set things up so that new tabs open to the homepage
;; by default.
(defun nero-new-tab (name)
  "Open a new tab."
  (interactive (list (read-string
                      (concat "Name (default: "
                              "): ")
  (nero-set-tab name t))

;; tabs are essentially just labeled lists of histories and futures.
(defun nero-set-tab (name &optional blank)
  "Set up a new tab named NAME.
If optional argument BLANK is non-nil, the tab will be empty,
otherwise it will contain a copy of the current browsing history.
A \"tab\" in nero is a list made up of a name of the tab (NAME,
in fact), and the associated full browsing history and browsing
  (interactive (list (read-string
                      (concat "Name (default: "
                              "): ")
  (setq nero-tabs
        (add-to-list 'nero-tabs
                     ;; we need to share a lot of structure...
                     (let ((newtab (list 'foo)))
                       (setcar newtab name)
                       (setcdr newtab (list nero-history))
                       (setcdr (cdr newtab) (list nero-future))
  ;; switch to the tab we just created.
  (nero-get-tab name)
  (when blank

(defun nero-default-new-tab-name ()
  "Generate the default name of a new tab."
  (concat "Tab " 
           (1+ (length nero-tabs)))))

(defun nero-get-tab (name)
  "Retrieve and activate the tab NAME."
  (interactive (list (completing-read "Tab: " 
                                      (mapcar #'car nero-tabs))))
  (unless (equal name "")
    (let ((newtab (assoc name nero-tabs)))
      (when newtab
        (setq nero-tab name
              nero-history (second newtab)
              nero-future (third newtab))
        (nero-restore-page (car nero-history))))))

(defun nero-reload ()
  "Reload the current url.
This is useful if you suspect its contents might have changed."
  (nero-browse-url (caar nero-history)))

(defun nero-restore-page (page)
  "Utility to display a page that has been saved in nero's history.
This command does not itself change `nero-history'/`nero-future'."
  (switch-to-buffer (get-buffer-create " *Nero Refs*"))
  (delete-region (point-min) (point-max))
  (insert (third page))
  (switch-to-buffer (get-buffer-create "*Nero*"))
  (delete-region (point-min) (point-max))
  (insert (second page))
  (goto-char (fourth page))
  (when nero-long-thread
    (setq nero-ariadnes-thread 
          (cons (first page) nero-ariadnes-thread))))

(defun nero-back ()
  "Display the previous page you visited."
  (if (eq (length nero-history) 1)
      (message "Already at beginning of history.")
    (setq nero-future (cons (car nero-history) nero-future)
          nero-history (cdr nero-history))
    (nero-restore-page (car nero-history))))

(defun nero-back-to-beginning ()
  "Display the first page you visited.
Runs `nero-mark-page' first."
  (while (> (length nero-history) 1)

(defun nero-forward ()
  "Display the next page you visited."
  (if (not nero-future)
      (message "Already at end of future.")
    (setq nero-history (cons (car nero-future) nero-history)
          nero-future (cdr nero-future))
    (nero-restore-page (car nero-history))))

(defun nero-forward-to-end ()
  "Display the final page you visited.
Runs `nero-mark-page' first."
  (while nero-future

(defun nero-down (&optional handler)
  "Use the next link spotted in this buffer.
How we actually make use of this link depends on HANDLER.
See `nero-follow-link-internal'."
  (while (and (looking-at "[0-9]*\\]")
              (not (bobp)))
    (backward-char 1))
  ;; does not currently work with escaped brackets
  (when (search-forward-regexp nero-link-regexp nil t)
    (nero-follow-link-internal (match-string-no-properties 1) handler)))

(defun nero-follow-link-internal (number &optional handler)
  "Read in the NUMBER of a link and display the page it leads to.
If HANDLER is equal to 'wget, download; if it is equal to
'copy-link, copy the url accessed by link to the kill ring.
Otherwise, browse the page that link links to."
  (set-buffer (get-buffer-create " *Nero Refs*"))
    (goto-char (point-min))
    (when (search-forward-regexp 
           (concat " +" number "\\. \\([^ \n]*\\)") nil t)
      (run-hooks 'nero-follow-link-hook)
      (case handler
         (nero-download-url (match-string-no-properties 1)))
         (kill-ring-save (match-beginning 1) (match-end 1)))
         (buffer-substring-no-properties (match-beginning 1)
                                         (match-end 1)))
         (nero-browse-url (match-string-no-properties 1)))))))

(defun nero-restore-tab ()
  "Function run when `nero-follow-link-hook' is run and we're following a tab 
  (let ((tab (buffer-substring (1+ (point)) (line-end-position))))
    (nero-get-tab tab)))

(defun nero-download-link (&optional number)
  "Like `nero-follow-link' except that the page is saved, not displayed."
  (interactive "P")
  (if number
       (int-to-string (prefix-numeric-value number)))
    (nero-down 'wget)))

(defun nero-kill-ring-save-current-url ()
  "Copy the current url to the kill ring.
If `x-select-enable-clipboard' is non-nil and you are running
Emacs under X, this makes it easy to paste the url into other programs."
    (insert (caar nero-history))
    (kill-ring-save (point-min) (point-max)))
  (message "Current URL copied to kill-ring."))

(defun nero-kill-ring-save-current-link ()
  "Copy the current link to the kill ring.
If `x-select-enable-clipboard' is non-nil and you are running
Emacs under X, this makes it easy to paste the url into other programs."
  (save-excursion (nero-down 'copy-link))
  (message "URL of current link copied to kill-ring."))

(defun nero-toggle-images ()
  "Toggle display of images in nero."
  (setq nero-images (not nero-images)))

(defun nero-toggle-links-display ()
  "Toggle whether or not links are visible in the current page.
Even if the links are not visible, you can still follow them
using `nero-follow-link', and they will still be copied to
another buffer if you select and copy text that contains them.
See `nero-toggle-links-exist' for a \"firmer\" function that
accomplishes something similar."
    (goto-char (point-min))
    (setq nero-links-display (not nero-links-display))
    ;; what to do now?
    (if nero-links-display 
        ;; show links
        (remove-text-properties (point-min) (point-max) 
                                '(invisible t
                                  intangible t))
      ;; hide links
      (while (re-search-forward nero-link-regexp nil t)
        (set-text-properties (match-beginning 0)
                             (match-end 0)
                             '(invisible t
                               intangible t))))))

(defun nero-toggle-links-exist ()
  "Toggle whether or not links exist in the current page.
If there are links (as is typically the case), they will be
deleted.  But if there are no links (as will be the case after
running `nero-toggle-links-exist' an odd number of times) running
the function again will cause the links to be automagicially
restored.  See `nero-toggle-links-display' for a \"softer\"
function that accomplishes something similar."
    (goto-char (point-min))
    (setq nero-links-exist (not nero-links-exist))
    ;; what to do now?
    (if nero-links-exist
        ;; replace the links we saved
        (let ((offset 0))
          (while nero-links-sanctuary
            (goto-char (+ (first (car nero-links-sanctuary)) offset))
            (let ((str (second (car nero-links-sanctuary))))
              (insert str)
              (setq offset (+ offset (length str))))
            (setq nero-links-sanctuary (cdr nero-links-sanctuary))))
      ;; delete the links
      (while (re-search-forward nero-link-regexp nil t)
        (let ((match (match-string 0)))
          (replace-match "")
          (setq nero-links-sanctuary
                (append nero-links-sanctuary 
                        (list (list (point) match)))
                nero-links-exist nil))))))

;; this could be done in a more portable way by looking at the first
;; four letters of the regexp string.  But the current way it is
;; written will make it easy to extend this function to cycle through
;; a longer list of things to escape.
;; However, I'm not really sure that it currently works well at all.
; (defun nero-toggle-escape-brackets ()
;   "Deals with webpages that have numbers in double brackets.
; There aren't many of these, but they could be annoying.  The
; reason that this mode is not the default is that pages more
; frequently will much more frequently have links in brackets, and
; we want to be able to recognize those."
;   (interactive)
;   (cond 
;     ((equal nero-link-regexp "\\[\\([0-9]+\\)\\]")
;      (setq nero-link-regexp "[^[]\\[\\([0-9]+\\)\\]"))
;     ((equal nero-link-regexp "[^[]\\[\\([0-9]+\\)\\]")
;      (setq nero-link-regexp "\\[\\([0-9]+\\)\\]"))
;     (t nil))
;   ;; refontifying doesn't seem to work
;   (setq nero-font-lock-keywords 
;        (list `(,nero-link-regexp . font-lock-keyword-face)))
;   (font-lock-fontify-buffer))

(defun nero-follow-link (&optional number)
  "By default, opens the page linked to by the next link in the buffer.
See `nero-move-to-next-link' for the definition of \"next link\".
If a numerical argument is specified, open the page linked
to by the link bearing that NUMBER."
  (interactive "P")
  (if number
       (int-to-string (prefix-numeric-value number)))

(defun nero-move-to-next-link ()
  "Position the cursor on the next link that appears in the buffer.
The \"next link\" is any link such that the cursor is not past
the the right brace that denotes the link's end, and is not
before the right brace of any other link."
  (search-forward-regexp nero-link-regexp nil t)
  (backward-char 1))

(defun nero-home ()
  "Open `nero-homepage' with `nero-browse-url'."
  (nero-browse-url nero-homepage))

(defun nero-bookmarks ()
  "Open `nero-bookmarks' with `nero-browse-url'."
  (nero-browse-url nero-bookmarks))

(defun nero-bookmark-current-url (name)
  "Add a bookmark for the current page to the `nero-bookmarks' file.
NAME is a user-specified string that says what to call the bookmark."
  (interactive "MBookmark name: ")
    (insert "<LI><a href=\"" (caar nero-history) "\">" name "</a>\n")
    (write-region (point-min) (point-max) nero-bookmarks t)))

(defun nero-bookmark-current-link (name)
  "Add a bookmark for the current link to the `nero-bookmarks' file.
NAME is a user-specified string that says what to call the bookmark."
  (interactive "MBookmark name: ")
    (insert "<LI><a href=\"" (nero-down 'return-link) "\">" name "</a>\n")
    (write-region (point-min) (point-max) nero-bookmarks t)))

(defun nero-bookmark-arbitrary-url (name URL)
  "Add a bookmark for the current page to the `nero-bookmarks' file.
NAME is a user-specified string that says what to call the bookmark."
  (interactive "MBookmark name: \nMURL: ")
    (insert "<LI><a href=\"" URL "\">" name "</a>\n")
    (write-region (point-min) (point-max) nero-bookmarks t)))

;; figure out exactly when to apply this.  Can't necessarily do it
;; "always" or we would mess up bookmarking of google pages. Ah-ha!
;; How about if we do it in the  *Nero References buffer?
;; That would certainly seem to be the way to go, if we can't
;; figure out how to make google be nicer and not mark things up.
;; Presumably normal browsers use redirection?  Maybe we could set
;; it so that this ungooglifies whenever a link is followed from
;; a page containing google results (saving lots of replacements
;; that don't need to be done).  This is sorta like doing the
;; redirection, except it is presumably somewhat faster ;).
(defun ungooglify-url (url)
  "Google may doctor up a URL that you find using it.
This function undoes this unseemly behavior."
   (replace-regexp-in-string "&[^+]*?$" ""
    (replace-regexp-in-string "\\(^.*?q=\\)" "" url)))

(defun nero-hide ()
  "Restore the window configuration that was active before nero first ran."
  (set-window-configuration nero-old-layout))

(defun nero-finished ()
  "Quit this nero session."
  (kill-buffer "*Nero*"))

(defun nero-wind-down ()
  "Kill nero's associated buffers, restoring all windows and variables."
  (when (equal (buffer-name) "*Nero*")
    (when (buffer-live-p (get-buffer " *Nero Refs*"))
      (kill-buffer " *Nero Refs*"))
    (when (buffer-live-p (get-buffer "*Nero Tabs*"))
      (set-buffer "*Nero Tabs*")
      (kill-buffer "*Nero Tabs*"))
    (setq nero-buffer-in-nero-mode nil)

(defmacro nero-defelvis (name baseurl separator &optional trailing)
  "Create an elvis to use with nero.
Elvi were made popular by the Shell Users' Revolutionary Front
Rage Against The Web.  They are functions for interacting with
useful web services through a simple non-web interface.  This
macro implements the same basic strategy, this time for
Emacs/Nero users.

NAME is how you might refer to the elvis in prose. BASEURL is the
beginning part of the URL for the service.  SEPARATOR is the
symbol to substitute for spaces in the final url we build from
the arguments given to the function defined by `nero-defelvis'.
that implements the elvis.  TRAILING, if present, plays a role
similar to that of BASEURL, but at the end of the built-up
string.  The names of elvi defined using this function are
automatically added to the list `nero-elvi'."
  (declare (indent defun))
  `(add-to-list 'nero-elvi
                (defun ,(intern 
                         (concat "nero-query-" 
                                   " " "-" name))))
                  ,(concat "The " name " elvis for nero.
SEARCH-STRING will be submitted to the service and you will be 
shown the results, just as if you were using a \"normal\" web
                  (interactive "MSearch string: ")
                   (concat ,baseurl 
                            " " "+" search-string)
                           (if ,trailing

(nero-defelvis "Google" 

(nero-defelvis "Help GNU Emacs"

(nero-defelvis "GNU Emacs Sources"

(nero-defelvis "EmacsWiki"

(nero-defelvis "Free Software Directory" 

;; wikipedia search is actually a google search 
(nero-defelvis "Wikipedia"

;; PlanetMath search is actually a google search
(nero-defelvis "PlanetMath"

;;; nero.el ends here

reply via email to

[Prev in Thread] Current Thread [Next in Thread]