Remix.run Logo
▲ LoganDark 4 hours ago

Curious, is elisp particularly good at that (editing a process while it's running)? I've never experienced a system like that before, so I'm curious.

▲a_e_k 5 minutes ago | parent | next [-]

If you've read Pratchett's Discworld, I like to think of Emacs as an Igor: it's a loyal servant that can swap out, share its own parts, and rebuild itself on the fly.

But to answer your question, It's not just elisp itself, but rather that Emacs is basically a live elisp interpreter process with an editor UI on top, and the design is something of a ball-of-mud with few boundaries.

With typical systems you have plugins and they have fairly narrow interfaces. If you want to do something that's not covered by the plugin API, you're pretty much out of luck.

With Emacs, everything is open as a free-for-all to extend or redefine, so you can monkey-patch pretty much anything. Here's an example from my init.el:

    (define-key global-map [remap move-beginning-of-line] 'move-beginning-of-line-dwim)
    (defun move-beginning-of-line-dwim ()
      "Cycles between the beginning of text on the line, the actual
    beginning of the line, and the beginning of the body of a comment
    on the line.  If not in one of those three places, it starts at
    the beginning of the text.  Basically, it's a smarter HOME
    command."
      (interactive "^")
      (comment-normalize-vars t)
      (unless (and (bolp) (comment-search-forward (pos-eol) t))
        (let ((cur (point)))
          (beginning-of-line-text)
          (if (= cur (point))
              (beginning-of-line)))))
By default, the HOME key and Ctrl-a are bound to the `move-beginning-of-line` (defined in `simple.el`, one of the elisp source files that comprises vanilla Emacs). That `define-key` with the `remap` tells Emacs that everywhere in the global keymap that it sees the default `move-beginning-of-line`, it should replace it with my custom `move-beginning-of-line-dwim`.

Alternatively, I could have just done `(defun move-beginning-of-line ...)` and simply redefined `move-beginning-of-line` wholesale and it would probably have worked fine. But this is a little cleaner, IMO. Either way, now when I hit HOME key it cycles between column 0, the first non-whitespace character, and the first character in the body of a trailing comment.

On top of that, I don't need to have a formal plugin or anything. I can take the above code, paste it into an elisp buffer somewhere, put the cursor somewhere in or after the `(defun ...), hit Ctrl-x Ctrl-e and now that function is defined within Emacs (and callable by other functions). Same thing with the `(define-key ...)`: put the cursor on it, Ctrl-x Ctrl-e, and the binding is updated. No ceremony.

And for testing, I can edit the function freely, hit Ctrl-x Ctrl-e and now my edited version is the live one in the running Emacs process. It's a lot like a scripting language REPL except within the editor UI.

And in addition, Ctrl-h Ctrl-f tells you about any function that Emacs knows, defaulting to whatever function name the cursor is on. So if I put the cursor on `move-beginning-of-line-dwim` and hit Ctrl-h Ctrl-f RETURN, I get:

    <home> runs the command move-beginning-of-line-dwim (found in global-map),
    which is an interactive interpreted-function.

    It is bound to C-a and <home>.

    (move-beginning-of-line-dwim)

    Cycles between the beginning of text on the line, the actual
    beginning of the line, and the beginning of the body of a comment
    on the line.  If not in one of those three places, it starts at
    the beginning of the text.  Basically, it’s a smarter HOME
    command.

    [back]
Same thing for Ctrl-h Ctrl-v for variables and settings. The documentation that it shows is generated directly from the doc strings and live editor state. Ctrl-h Ctrl-k will prompt for a key and show me a key binding, so Ctrl-h Ctrl-k HOME will give me the above documentation now too. Ctrl-h Ctrl-b shows all applicable keybindings, etc.

Now I'd mentioned that you can monkey-patch anything. In practice that could cause chaos if you don't take care. A pretty common idiom in Emacs to do this more cooperatively are "hooks". These are just variables that some package, internal or external, defines to hold a list of functions to callback. The package iterates the list and calls them whenever the given event happens and you can add or remove your own function to the list to get that call. Think signals and slots, if you seen that design before. And these are pretty ubiquitous. As an example from my init.el:

    (add-hook 'font-lock-mode-hook 'font-lock-mode-setup)
    (defun font-lock-mode-setup ()
      "Make fixme tags standout."
      (unless (derived-mode-p 'special-mode)                      ; Don't break list-{faces,colors}-display
        (font-lock-add-keywords nil
         '(("\\<\\(?:AEK\\|NOTE\\|FIXME\\|TODO\\|XXX\\)\\>\\(?:([a-z]+)\\)?:?" 0 'font-lock-warning-face prepend)))))
So whenever syntax highlighting is started up in a buffer, it calls the font-lock-mode-hook callbacks, and that runs my code to register a regexp to highlight my initials and other fixme tags.

And if all that's not enough, Emacs also has an "advice" system which lets you register code to run before, after, around, etc., any existing function. (And then later unregister it if you wish.) There's also a built-in elisp debugger to break into a function and step through it.

So basically, the key I'd say is that there's no real privileged code in Emacs. You can redefine pretty much anything to taste, and do so without even having to reload Emacs. The downside is that it's up to you to decide what to trust. It's on you to avoid breaking it, and you probably want to be choosy about the packages you run.

▲Karrot_Kream 27 minutes ago | parent | prev | next [-]

Elisp is homoiconic and has a strong culture of REPL development, but really the thing that makes elisp so nice is that it has decades of use to iterate on an editor. There's an attempt to do this with Common Lisp that's quite cool, called Lem, but the sheer amount of person-years that have gone into making emacs and elisp a conjoined system makes it a pleasure to use and iterate on. Lem, in comparison, is building up all the UX associated with a "live programming environment" that just comes out of the box with elisp (though I, personally, prefer Common Lisp as a language/environment.)

Pre-LLM I used to spend the occasional afternoon at work when I was a bit demotivated just adding new features to my emacs or fixing small papercuts in the environment. Now I largely have the LLM work on it for me, though I separate out LLM code from my own by loading them separately in my config.

▲iLemming 3 hours ago | parent | prev | next [-]

Any Lisp is good at editing live process, you just need to give it a REPL. Because of homoiconic nature of Lisp, every step in Read-Eval-Print-Loop differs. Lispers sometimes refer to Lisp REPL as the "true REPL", because non-homoiconic REPLs are rather "interactive shells" in comparison.

Clojurescripters for example can use nbb with the connected REPL and build web-scrapers while interactively "clicking" on buttons and exploring DOM directly from the editor.

In case of Emacs, it is not [just] an editor, technically it is a Lisp REPL loop with a built-in text editor. That's why you can virtually change any aspect of it on the fly, without ever having to restart it.

What's crazy that this allows to have pretty much anything "programmable". Even keyboard macros in Emacs are just series of Lisp instructions. You can compose a macro and then look at its underlying code, modify it and execute it.

▲drob518 22 minutes ago | parent | prev | next [-]

I do this everyday with Emacs live editing a Clojure process.

▲jerf 4 hours ago | parent | prev | next [-]

There are a number of environments and languages that have the capability to reload things extensively at runtime. elisp is mostly nice because it is polished, but it isn't necessarily the "best". Erlang has a claim on being better at it, probably. And while most scripting languages are capable of it, that doesn't mean you can just drop one in and go necessarily. It helps to have an environment where that sort of interactivity is first-class and in constant use and testing, which is where the polish comes from.

▲fmbb 4 hours ago | parent [-]

Erlang the runtime sure. But Emacs is an application with these properties. There is no editor like Emacs for Erlang (some small clones I am sure) and no other user application utilizing this feature.

Smalltalk is a development environment and an applications platform. The development tools are as extensible and user customizable as Emacs. And like Erlang it is also a runtime you can package software for. Any GUI or server software. For Emacs you can probably do the same, but I’m not convince many users would find the GUI familiar.

▲knome 4 hours ago | parent | prev | next [-]

you can replace or wrap arbitrary functions or update values freely in emacs. most of the editor is written in elisp. only the primitives are in c.

you can write (arbitrary (lisp)) and then just put the cursor after the end parenthesis and hit `Ctrl-x Ctrl-e` to evaluate it. It works just like you had typed out something in a python repl, only you can inspect, add, or update anything inside of your running copy of emacs.

As an example, I've had a chunk in my ~/.emacs for 15+ years that rewrites a few builtin functions to prefix all of the backup files emacs makes with a `.` because I don't like to see them in `ls` listings but still want them local to where they were made should I want to diff them against the current version. (the default is just a ~ suffix, and there's a common directory relocation many people use to pile them all in a backups directory somewhere)

As another, I wanted to be able to punch a button and drop the current datetime into a file, for files where I keep records and the like, so I added this:

    (defun my-date-insert ()
      (interactive)
      (insert (substring (shell-command-to-string "date") 0 -1 ))
      )
    
    (global-set-key [f9] 'my-date-insert)
Albeit, you needn't shell out to get a date in emacs, I dropped in in their years ago on a whim and just used what I knew at that time to toss it together.
▲a_e_k an hour ago | parent [-]

`(insert (format-time-string "%c"))` should do the trick, though you might need to change the format string to taste.

▲tetraodonpuffer 4 hours ago | parent | prev [-]

well if you're in emacs you can C-x C-e evaluate whatever, and if you eval an edited version of whatever function emacs is calling, it just works as emacs calls the thing by symbol name; conceptually it's basically your editor is an elisp repl, so everything can be changed at any time (in general, things like defvar you'd need to setq and so on)