Skip to content

sminez/ad

Repository files navigation

ad :: an adaptable text editor

Build crates.io version docs.rs

ad (pronounced A.D.) is an attempt at combining a modal editing interface of likes of vi and kakoune with the approach to extensibility of Plan9's Acme. Inside of ad text is something you can execute as well as edit.

It is primarily intended as playground for experimenting with implementing various text editor features and currently is not at all optimised or feature complete enough for use as your main text editor.

That said, if this sounds like something you might find interesting then please to take a look and see what you think! For now there isn't a whole lot of user facing documentation other than the built in :help section, so you will need to read through the source code and GitHub issues to learn about what is and is not implemented.

screenshot

Project Status

ad is stable enough and and supports sufficient features that you can try it out and see what you think. That said, there is currently very little documentation and there are likely to be a variety of bugs and crashes in places that I've not managed to fully track down yet. If you do try it out and spot something that is broken, please raise an issue on GitHub so I can look into it.

You have been warned!

Contributing

The project as a whole isn't particularly well suited for external contributors in its current state so please do raise an issue to discuss any proposed changes or feature requests first rather than directly opening PRs. Outside of minor bug fixes and typo corrections I am unlikely to be able to do anything other than close PRs that have been opened without prior discussion of the issue they are intending to address.

Getting started

Packaging of the project to include the default config files and helper scripts isn't currently in place, so the recommended way to try out ad is to clone this repo and compile from source:

$ git clone [email protected]:sminez/ad.git
$ cd ad
$ cargo install --path .
$ make setup-dotfiles

From there you should be able to open ad and run the :help command to view the built-in help. If you would like to watch a tour of how ad works there is one available here.

Please be aware that given the early stage of the project and frequent changes to the codebase, the exact content of the video tour may not accurately reflect the current state of ad.

The design of ad

ad is aiming to be a hybrid of the pieces of various editors that I find most useful:

  • vim style modal editing to allow for convenient key bindings
  • convenient text navigation and selection from vim/kakoune
  • mini-buffer based user defined minor modes from emacs
  • sam/acme style editing commands for larger editing actions
  • acme style extension through exposing editor state and functionality for external client programs.
  • support for mouse based navigation and selection but not requiring that as the main way of using the editor like in acme. That's fine for desktop but most of the time I'm working with a laptop which makes that far too clunky.

ad is not trying to replace vim (or kakoune, or emacs) in terms of being a massively hackable editor. Rather it is trying to follow the philosophy of acme in being an integrating development environment (rather than integrated). By which I mean that the aim is to provide a comfortable editing environment to work in that supports direct interaction with external tools and programs from the outside rather than pulling everything in.

Repo structure

Given the (arguably questionable) goal of implementing everything from scratch, there is a fair amount of functionality included in ad which in turn is split out into a number of modules within the crate. For now, I'm not structuring things as individual crates but that may change in future.

Modules

This is a non-exhaustive list of some of the more interesting parts of the internals of ad

  • buffer/internal: a gap buffer implementation for the internal state of a Buffer.
  • dot: manipulation of the current selection in a given buffer (including vim-like motions)
  • exec: minimal implementation of the core of the sam editing language
  • fsys: virtual filesystem interface to the editor state in the style of acme
  • ninep: 9p protocol implementation that backs the fsys module
    • Now moved out to its own crate with source code available here.
  • regex: custom regex engine that is able to work on character streams. This is nowhere near as performant as the regex crate (obviously) but it allows for some flexability in tinkering with the exec command language.
  • trie: trie data structure for handling sequence based keybindings

Why?

I've used vim for years now (more recently neovim and kakoune) and I really love the core editor itself. A while back I discovered acme through a fantastic screencast from Russ Cox, showing how you could interface with it via plan filesystem protocol, allowing you to run pretty much whatever you want inside of the editor (in any language) so long as you can interact with that protocol. That I absolutely love, but the lack of modal editing and requirement to use the mouse when I'm sat with my laptop is proving hard to get used to, so I set about looking at how to port over some of the acme ideas into vim (namely the load/execute semantics via the plumber and the idea of exposing the editor state in a really simple way to client programs).

Turns out, vim has a lot more built into it that I was previously aware (and I've been hacking on my vimrc for years now) which was more than a little scary. What I want is a small, usable editor that I can hack on.

So...How hard could it be?

Simplicity

For things that are going to be core parts of the experience (bindings, per-filetype configuration) I'm just going to hard code stuff. I'll try to do it in a way that makes it easy to update / change but the whole thing will be a lot easier to write if there isn't too much config parsing.

That said, the more I work on this, the more I wonder if it might be interesting to structure ad in the same way as penrose and have it as a library for writing your own text editor? That would require some restructuring but might be interesting to explore...

Goals

  • Simple modal editing to the extent that I use VIM
  • Sed/Sam style edit commands
  • Acme style use of external commands rather than an embedded language:
    • Exposing current buffer / window state to external programs
    • Exposing events to external programs
    • Accepting events from other programs
  • Virtual buffers for command output that can be hidden

Sam style structural regular expressions

One aim of this project is to provide an implementation of "Structural Regular Expressions" as first presented (to my knowledge) in the Sam text editor from plan9 by Rob Pike. This tutorial from Pike covers the command language of Sam which I am using as a starting point for the command language for ad. So far I'm not aiming for a perfect match with the functionality of Sam or Acme but I am looking to make use of the pieces that feel particularly useful. As the project develops I may well end up pulling in more but for now I'm happy to have a decent starting point for an implementation of the structural regular expression engine.

There is still a fair amount to do but so far the idea is to allow for repeated narrowing and looping over sub-matches within a buffer or file loaded from disk. (A streaming interface working over stdin is coming but I need to have a think about how best to buffer the input and track partial matches in the regex engine to avoid slowing things down too much or requiring the engine to buffer and collect all of its standard input before matching).

The current engine can be used via the -e and -f flags to run ad in headless mode, but hooking things into the interactive editor directly should be coming soon. For now, here is a demo of some simple functionality of the engine:

$ cat examples/exec_scripts/result_fns.ad
,                              # set dot to be the full input (not required as this is the default)
x/fn@*?\{/                     # select all Rust function signatures up to the opening brace
g/->.*Result.*\{/              # keep those that return some form of Result
x/fn (\w+)@*?-> (.*?) \{/      # extract the function name and return type from the signature
p/($FILENAME) $1 returns $2/   # print them along with the filename using a template


$ ad -f examples/exec_scripts/result_fns.ad src/**/*.rs | head
(src/buffer/buffers.rs) open_or_focus returns io::Result<()>
(src/buffer/dot/cur.rs) fmt returns fmt::Result
(src/buffer/dot/mod.rs) fmt returns fmt::Result
(src/buffer/dot/range.rs) fmt returns fmt::Result
(src/buffer/edit.rs) fmt returns fmt::Result
(src/buffer/mod.rs) new_from_canonical_file_path returns io::Result<Self>
(src/exec/parse.rs) execute returns Result<(usize, usize), Error>
(src/exec/parse.rs) step returns Result<(usize, usize), Error>
(src/exec/parse.rs) try_parse returns Result<Self, Error>
(src/exec/parse.rs) validate returns Result<(), Error>