Back to Emacs while in Nyxt: how to edit the web in a Lispy editor

Im just sharing this link that I’ve found on Reddit

The Reddit Post

The Article/Blog With The Code

Let’s start simple. Nyxt allows you to set an editor with which to complete forms. This is how I configure Nyxt to achieve that.

(DEFINE-CONFIGURATION (BUFFER WEB-BUFFER)
  ((DEFAULT-MODES (APPEND '(EMACS-MODE) %SLOT-DEFAULT))))
(DEFINE-CONFIGURATION BROWSER
  ((EXTERNAL-EDITOR-PROGRAM "/usr/bin/emacsclient")))

The first bit is about having Emacs keybindings in Nyxt. The second expression sets emacsclient as the default external editor.

Add these lines in your auto-config path. That is located at ~/.config/nyxt/auto-config.lisp on Linux.

When you are on the input of a form, you just have to use C-x ’ (like for Org Mode source blocks) and you will open Emacs. At that point insert the text you need and use C-x #. You shall be back in Nyxt.

A word of caution: this works only for forms for now. Not sure why this limitation, I am eventually going to ask the devs. It would be cool to have this for anything. Imagine if we could set also the fittest Emacs mode (I am thinking of editing GitHub files online with Emacs)!

Actually we may not even need the help of Nyxt devs for that! We can change Nyxt at run time with Slime, remember?

For now let’s go easy again. I assume you have Nyxt running. Also you should have a nice Slime REPL available to edit it on the run.

Let’s start from letting Nyxt speak to Emacs. Building over Pierre Neidhards’ Emacs Hacks, you can use the following.

(defun replace-all (string part replacement &key (test #'char=))
  "Returns a new string in which all the occurences of the part 
is replaced with replacement."
  (with-output-to-string (out)
    (loop with part-length = (length part)
          for old-pos = 0 then (+ pos part-length)
          for pos = (search part string
                            :start2 old-pos
                            :test test)
          do (write-string string out
                           :start old-pos
                           :end (or pos (length string)))
          when pos do (write-string replacement out)
            while pos)))

(defun eval-in-emacs (&rest s-exps)
  "Evaluate S-EXPS with emacsclient."
  (let ((s-exps-string (replace-all
                        (write-to-string
                         `(progn ,@s-exps) :case :downcase)
                        ;; Discard the package prefix.
                        "nyxt::" "")))
    (format *error-output* "Sending to Emacs:~%~a~%" s-exps-string)
    (uiop:run-program
     (list "emacsclient" "--eval" s-exps-string))))

The function eval-in-emacs allows you to send Elisp code to emacsclient.

If you managed to run that in your Slime, then try to run also this.

(define-command my/playing-around ()
     "Query ."
     (eval-in-emacs
        `(message "hello from Nyxt!")))

If you do that, you will find a new command in Nyxt! This will pop a message in your Emacs!! Nyxt has just called Emacs: how cool are we?!?

Now let’s try something fancier. What about jumping from a GitHub page opened in Nyxt to our local source file and back?

Say I am on writer-word-goals/wwg.el at master · ag91/writer-word-goals · GitHub and I want to visit a line of it in Emacs.

This is the function I need for Nyxt.

(define-command my/display-wwg-selection ()
     "Something else."
     (let ((selection (%copy)))
       (eval-in-emacs
        `(find-file "~/workspace/writer-word-goals/wwg.el")
        `(goto-char (point-min))
        `(search-forward ,selection))))

Now if I select some text in the page that belongs to the file, I will open the file in Emacs at that point.

What about jumping back from Emacs to Nyxt then?

This was a bit tougher to make it work due to my Common Lisp basic skills, but thanks to the amazing Atlas community I found out how.

We need to search a Nyxt buffer. This is already possible, but only via Nyxt’s prompt. We need to do a search via CL: is the Nyxt so programmable then?

The following is the CL code we need.

(nyxt/web-mode::highlight-selected-hint
 :link-hint
 (car
  (nyxt/web-mode::matches-from-json
   (nyxt/web-mode::query-buffer :query "someString")))
 :scroll 't)

These are a few lines of Common Lisp that highlight and scroll to the first match over the query string. The function nyxt/web-mode::query-buffer is finding all the matches. The function nyxt/web-mode::matches-from-json converts matches to a Lisp format. Finally, highlight-selected-hint is running some JavaScript (compiled from ParensScript!) to select the match on the page.

Using our emacs-with-nyxt infrastructure, we can get an Elisp function for jumping back to Nyxt from Emacs.

(defun emacs-with-nyxt-search-first-in-nyxt-current-buffer (string)
  "Search current Nyxt buffer for STRING."
  (interactive "sString to search: ")
  (unless (slime-connected-p) (emacs-with-nyxt-start-and-connect-to-nyxt))
  (emacs-with-nyxt-slime-repl-send-string
   (format "(nyxt/web-mode::highlight-selected-hint :link-hint (car (nyxt/web-mode::matches-from-json (nyxt/web-mode::query-buffer :query \"%s\"))) :scroll 't)" string)))

That was an example of what is achievable! I found this promising given that I started using Nyxt a couple of weeks ago. And I am also becoming familiar with Common Lisp!!!

1 Like

author here: thanks for sharing that! Related thread I opened about it: Emacs for filling more than forms input?

1 Like

Nice. Great to see you around :slight_smile:

1 Like

This stuff is so cool @Andrea , I’m really interested to see what other ideas for integrations you can come up with, as always, I’ll do my best to help support :slight_smile:

1 Like