ALE – arcfide's line editor
ALE is a line editor that I have created because I can. One of my all time favorite editors to use in the classic Unix ed(1) editor. Line editors are, in my opinion, grossly under-appreciated for their workflow and power as interactive editors. With that said (or should I say, sed?), the pre-eminent example of this in the modern computing world, ed(1), does have a few issues that I felt I could improve upon.
Furthermore, I was tired of the infrastructure setup that is required in order to use a line editor inside of modern operating systems. They aren't convenient to use by default in GUI environments, and there isn't a good, clean, fast way to "just get" ed(1) on Windows, because so much of ed(1)'s design relies on having the wider UNIX ecosystem available to it. This means, really, that if I wanted to use ed(1) on Windows, I was more or less resigned to also installing a rather heavyweight UNIX environment as well. This greatly interferes with the simplicity of the editor itself, as well as integration.
Since almost all of my work is done with APL, I realized the potential value of having an easy to use line editor that was written in a deeply integrated way with the APL language. APL is itself a pretty good textual editing language, and by simply creating a "dictionary" of editing commands, I could leverage the power of the existing Dyalog APL session to do almost all the heavy lifting for me.
The result is an editor that retains the benefits of the ed(1) editor with some noticeable improvements in flexibility, while simultaneously making the editor itself simpler, easier to maintain, and amenable to customized extensions, macros, and other composability and programmability benefits usually only seen in more complex editors, all with basically zero real additional complexity.
In short, ALE is a programmable, composable, but simpler and more streamlined, syntactically consistent replacement for the venerable ed(1).
Lines are origin-1 based references to lines in a file, with the 0th line referencing the line before the first line in a file. Address ranges are a pair of lines where the first line must be less than or equal to the second line. They specify a contiguous region of lines in a file.
Traditional ed(1) had a number of different addressing shortcuts. We do not have any of that in ALE at the moment, and instead prefer to rely on the fact that we have full arithmetic expressive power through APL. This means that the number of built-in addressing short cuts is relatively limited. Instead, you should simply write an APL expression to give you the appropriate range if you should want it.
- _
- The address of the current line.
- _a
- Equivalent to `1,≢∆`.
- _s regex
- Gives the next line matching regex with wraparound.
- _r regex
- Gives the previous line matching regex with wraparound.
- a line
- Appends text to the buffer after the addressed line. Input mode is used to enter text. The current address is set to the last line entered.
- c range
- Change the addressed range in the buffer. Replacement text is entered in input mode. The current address is set to the last line entered.
- d range
- Deletes the addressed lines and sets the current address to the line after the deleted line if there is one, or to the line before the deleted range if there is not.
- e file
- Edits file and sets the default filename. Clears and replaces the existing edit buffer. The current address is set to the last line read.
- efn expr
- Edits the vector of strings returned by expr. The current address is set to the last line read. This function replaces the use of the shell commands with APL expressions.
- E file
- Unconditionally edits file. This is like e but it will erase a dirty buffer without warning.
- (1,≢∆){...}g 're'
- Applies the function `{...}` to the addressed lines matching regular expression `'re'`. For each match, the function is applied once after setting the current line to the matching line. The right argument to the function is the line number of the match. The line number is set to the last line modified by one of the commands issued inside of the function.
- (1,≢∆)G 're'
- Works much like the `g` command except that it prompts interactively for the commands to run. It does this for each matching line by first printing the line that matches, and then prompting with the evaluator to ask for the next commands to run. The format of the commands must be suitable for execution by the `eval` function.
- i line
- Insert text before the addressed line. Text is entered in input mode. The current address is set to the last line entered.
- j range
- Joins the addressed range. The range is replaced with a single line containing the joined lines. The current address is set to the joined line.
- line k string
- Marks the addressed line with the given string. Deleting or modifying the line will result in that mark being deleted. Commands that support it may use string in place of a line number.
- l range
- Print the addressed lines unambiguously. Otherwise works like the `p` command.
- range m line
- Moves the addressed range to right after the addressed line. The current address is set to the last line moved.
- n range
- Prints the range with line numbers. The current address is set to the last line printed.
- p range
- Prints the range. The current address is set to the last line printed.
- q
- Quits **ALE**.
- Q
- Quites **ALE** unconditionally.
- line r file
- Read file into the buffer after line. Sets the default filename to file if there was no default filename already set. Sets the current address to the last line read.
- line r expr
- Works the same way as the r command with a filename, but if the right argument to r is a vector of strings, then this will be used as the contents to read into the buffer. It does not set the filename, and the current address is set to the last line read.
- (_,_)s pat rep pos
- Performs regex substitution. The pat value is a string containing the regex to match against. The rep string is a regex replacement pattern. The optional pos argument indicates how many replacements to do, the default being 0, meaning all possible replacements. If a non-zero, positive value n is given, then only the first n values will be replaced per line. If a negative value -n is given, then only the specific nth value will be replaced. The current line is set to the last line that was modified.
- (1,≢∆)t line
- Transfers (copies) the addressed range to right after the addressed lines. Current address is set to the last line transferred.
- u
- Undoes the effect of the last command that modified anything in the buffer and restores the current address to what it was before the command. The global commands 'g', 'G', 'v', and 'V' are treated as a single command by undo. 'u' is its own inverse; it can undo only the last command.
- (1,≢∆){...}v 're'
- Does the same thing as the `g` command but applies the function to lines that do *not* match 're' instead of those that do.
- (1,≢∆)V 're'
- Behaves like `G`, but like the `v` command, it operates on the not matching lines, rather than the matching lines.
- (1,≢∆)w file
- Writes the addressed lines to file. Previous contents of file are clobbered without warning. If there is no default filename, then the default filename will be set to file, otherwise it is not changed.
- (1,≢∆)wq file
- Writes buffer to file as with w and then executes the q command.
- (1,≢∆)W file
- Works like the `w` command but appends to the end of the file instead of overwriting the file.
- (_+1)z n
- Scrolls n lines at a time starting at the addressed line. The current address is set to the last line printed.
NOTE: For any of the regex-consuming commands above—namely _r, _s,
s, g, and v—supplying the null regex ''
is equivalent to supplying the
previously-used regex. This "regex history" is shared among all the commands
and is initialized to the null regex.
ed(1) | ALE | Comments |
---|---|---|
. | _ | Current line |
(.)a | a line | |
(.,.)c | c range | |
(.,.)d | d range | |
e file | e 'file' | |
e !command | efn expr | Use APL, not sh(1), expr must return vector of strings |
E file | E 'file' | |
f | ⍙ | Prints default filename |
f file | ⍙←'file' | Sets default filename |
(1,$)g/re/command-list | (1,≢∆){...}g're' | ⍵ is _ for each match |
(1,$)G/re/ | (1,≢∆)G're' | |
H | N/A | ALE errors are more verbose |
h | N/A | See H |
(.)i | i line | |
(.,.+1)j | j range | |
(.)k lc | line k string | |
(.,.)l | l range | |
(.,.)m(.) | range m line | |
(.,.)n | n range | |
(.,.)p | p range | |
P | N/A | APL session prompt is used |
q | q | |
Q | )off | |
($)r file | line r file | |
($)r !command | line r expr | expr must return vector of strings |
(.,.)s/re/replacement/ | (,)s 're' 'replacement' 1 | |
(.,.)s/re/replacement/g | (,)s 're' 'replacement' | |
(.,.)s/re/replacement/n | (,)s 're' 'replacement' ¯n | |
(.,.)s | N/A | Use the history mechanism |
(.,.)t(.) | range t line | |
u | Not yet implemented | |
(1,$)v/re/command-list | range {...}v 're' | |
(1,$)V/re/ | range V 're' | |
(1,$)w file | (1,≢∆)w file | Use ⍙ for default file |
(1,$)wq file | (1,≢∆)wq file | Use ⍙ for default file |
(1,$)w !command | fn ∆ | Apply APL function to buffer |
(1,$)W file | (1,≢∆)W file | Use ⍙ for default file |
(.+1)z n | (_+1)z n | |
($)= | N/A | Ranges print themselves |
(.+1)newline | N/A | Use z instead |
!command | ⎕SH 'command' | See Dyalog Documentation on ⎕SH |
Because ALE is basically just a slightly optimized APL session, you have at your disposal the full power of APL. This leads to some interesting features that ALE can "have" if you want it to.
ALE does not currently use a file buffer. This means that files are loaded into memory and stored there. This limits the size of files that can be loaded to the size of your main memory and the swap space that you have available on your machine.
All commands return an error code as the shy result of their execution. A 0 value indicates success, and a non-zero value indicates the type of error that occured.
ALE is very new software right now, and it may not be fully stable. Beware of this before using it in a mission critical environment.