An extensible POSIX-compatible implementation of the ed(1) text editor
git clone https://git.8pit.net/edward.git
1;;>| Text Editor Object 2;;> 3;;> Object for storing, inspecting, and modifying the editor state. 4 5;;> Create a new text editor object on a given (potentially) empty 6;;> `filename` string. If non-empty, an `edit-proc` needs to be provided 7;;> which implements the `E` command to initially read the file. 8;;> Furthermore, a `prompt` string must be provided and it must be 9;;> indicated whether the editor should start in `silent?` mode. 10 11(define (make-text-editor edit-proc filename prompt silent?) 12 (let* ((h (make-repl prompt)) 13 (b (make-buffer)) 14 (e (%make-text-editor filename h b 0 0 #f '() #f "" '() '() #f #f silent? #f))) 15 (unless (empty-string? filename) 16 ;; XXX: Don't print `?` if file doesn't exist. 17 (edit-proc e filename)) 18 e)) 19 20;;> Record type encapsulating the text editor state. 21 22(define-record-type Text-Editor 23 (%make-text-editor filename input buffer line last-line error marks state re 24 lcmd replace modified? last-modified? silent? help?) 25 ;;> Predicate which returns true if the given object was created using [make-text-editor](#make-text-editor). 26 text-editor? 27 ;; Name of the file currently being edited. 28 (filename 29 ;;> Returns the name of the file that is currently being edited. 30 text-editor-filename 31 32 ;;> Change the file that is currently being edited. 33 text-editor-filename-set!) 34 ;; Input repl for this text editor. 35 (input text-editor-repl) 36 ;; List of strings representing all lines in the file. 37 (buffer text-editor-buffer text-editor-buffer-set!) 38 ;; Current line in the buffer. 39 (line 40 ;;> Returns the current line in the internal editor buffer. 41 text-editor-line 42 text-editor-line-set!) 43 ;; Previous line in the buffer. 44 (last-line text-editor-last-line text-editor-last-line-set!) 45 ;; Last error message encountered (for h and H command). 46 (error 47 ;;> Returns a string representing the last encountered error message. 48 text-editor-error 49 text-editor-error-set!) 50 ;; Assoc lists of marks for this editor. 51 ;; XXX: Since data is never deleted from an assoc list this leaks memory. 52 (marks text-editor-marks text-editor-marks-set!) 53 ;; Symbol with previous cmd name executed by the editor or #f if none. 54 (state 55 ;;> Returns the symbol of the command previously executed by 56 ;;> the editor, on `#f` if no previous command was executed. 57 text-editor-prevcmd 58 text-editor-prevcmd-set!) 59 ;; String representing last encountered RE. 60 (re text-editor-re text-editor-re-set!) 61 ;; Last command executed by the shell escape editor command or '() if none. 62 (lcmd 63 text-editor-last-cmd 64 ;;> Update the last shell command executed via the shell escape editor command. 65 text-editor-last-cmd-set!) 66 ;; Last used replacement for the substitute command or '() if none. 67 (replace text-editor-last-replace text-editor-last-replace-set!) 68 ;; Whether the editor has been modified since the last write. 69 (modified? 70 ;;> Predicate which returns true if the current file has been 71 ;;> modified since the last write to a file (i.e. has unwritten data). 72 text-editor-modified? 73 74 ;;> Modify the modified state of the current file. 75 ;;> Set this to `#t` if the file has been modified. 76 text-editor-modified-set!) 77 ;; Whether the editor has been modified before the last command was executed. 78 (last-modified? text-editor-last-modified? text-editor-last-modified-set!) 79 ;; Whether the editor is in silent mode (ed -s option). 80 (silent? text-editor-silent?) 81 ;; Whether help mode is activated. 82 (help? 83 ;;> Predicate which returns true if help mode is activated (`H` command). 84 text-editor-help? 85 86 ;;> Enable help mode by passing a truth value to this procedure. 87 text-editor-help-set!)) 88 89(define (handle-error editor line msg) 90 (let* ((in (text-editor-repl editor)) 91 (prefix (if (terminal-port? (current-input-port)) 92 "" 93 (string-append 94 "line " (number->string line) ": ")))) 95 (text-editor-prevcmd-set! editor #f) 96 (editor-error 97 editor 98 (string-append prefix msg)))) 99100(define (handle-sighup editor)101 ;; Returns `#t` if writes to file succeeded and `#f` otherwise.102 (define (write-file filename lines)103 (guard104 (eobj105 ((file-error? eobj) #f))106 ;; TODO: If file exists behavior is unspecified107 (call-with-output-file filename108 (lambda (port)109 (lines->port lines port)))110 #t))111112 (when (text-editor-modified? editor)113 (let* ((buf (text-editor-buffer editor))114 (lines (buffer->list buf))115 (success? (write-file "ed.hup" lines)))116 (unless success?117 (write-file (path-join (user-home) "ed.hup") lines))))118 (exit))119120;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;121122;;>| Editor Interface123;;>124;;> High-level text editor interfaces.125126;;> Start the read-eval-print loop (REPL) of the editor. Within the127;;> REPL, command parsing is performed using the given `cmd-parser`.128129(define (editor-start editor cmd-parser)130 (define (execute-command line cmd addr)131 (guard132 (eobj133 ((editor-error? eobj)134 (handle-error editor line (editor-error-msg eobj))))135 (if (cmd-reversible? cmd)136 (editor-with-undo editor137 (lambda ()138 (editor-exec editor addr cmd)))139 (editor-exec editor addr cmd))140 (text-editor-prevcmd-set! editor (cmd-symbol cmd))))141142 (signal-mask! signal/quit)143 (set-signal-handler!144 signal/hup145 (lambda (signum)146 (handle-sighup editor)))147148 (repl-run149 (text-editor-repl editor)150 cmd-parser151 ;; Success continuation.152 (lambda (line res)153 (let ((cmd (cdr res))154 (addr (car res)))155 (execute-command line cmd addr)))156 ;; Failure continuation.157 (lambda (line reason)158 (handle-error editor line reason))159 ;; Interrupt continuation.160 (lambda ()161 (newline)162 (editor-error editor "Interrupt"))))163164;;> Run an interactive command within the text editor.165;;> The command is parsed using the provided `cmd-parser`.166167(define (editor-interactive editor cmd-parser)168 (let ((repl (text-editor-repl editor)))169 (repl-interactive repl170 cmd-parser171 (lambda (line reason)172 (editor-raise "parsing of interactive command failed")))))173174;;> Toggle visibility of the REPL prompt.175176(define (editor-toggle-prompt! editor)177 (let* ((repl (text-editor-repl editor))178 (prompt? (repl-prompt? repl)))179 (repl-set-prompt! repl (not prompt?))))180181;;> Returns the last executed shell command or raises an error if none.182183(define (editor-shell-cmd editor)184 (let ((lcmd (text-editor-last-cmd editor)))185 (if (null? lcmd)186 (editor-raise "no previous command")187 lcmd)))188189;;> Build a new [regex][posix-regex regex] object and handle regex syntax190;;> errors as editor errors. If the provided pattern is empty, the last191;;> used pattern is re-used, if there is no last-used pattern an editor192;;> error is raised.193;;>194;;> [posix-regex regex]: https://wiki.call-cc.org/eggref/5/posix-regex#make-regex195196(define (editor-make-regex editor pattern)197 (define (editor-pattern editor pattern)198 (if (empty-string? pattern)199 (let ((last-re (text-editor-re editor)))200 (if (empty-string? last-re)201 (editor-raise "no previous pattern")202 last-re))203 (begin204 (text-editor-re-set! editor pattern)205 pattern)))206207 (let* ((pattern (editor-pattern editor pattern))208 (regex (call-with-current-continuation209 (lambda (k)210 (with-exception-handler211 (lambda (eobj)212 (k (error-object-message eobj)))213 (lambda ()214 (k (make-regex pattern))))))))215 (if (regex? regex)216 regex217 (editor-raise regex))))218219;;> Access a replacement string in the editor context. If220;;> the provided replacement string `subst` is `'previous-replace`221;;> then the previously used replacement string is returned or an222;;> editor error is raised if there is no previous replacement string.223;;> Otherwise, (if `subst` is a string) then the previous replacement224;;> is updated and `subst` is returned.225226(define (editor-restr editor subst)227 (if (equal? subst 'previous-replace)228 (let ((last-subst (text-editor-last-replace editor)))229 (if (null? last-subst)230 (editor-raise "no previous replacement")231 last-subst))232 (begin233 (text-editor-last-replace-set! editor subst)234 subst)))235236;;> Return the currently configured filename, if no default is given it237;;> is an error if no filename is configured for the given editor.238239(define editor-filename240 (case-lambda241 ((editor) (%editor-filename editor))242 ((editor default)243 (if (empty-string? default)244 (%editor-filename editor)245 default))))246247(define (%editor-filename editor)248 (let ((fn (text-editor-filename editor)))249 (if (empty-string? fn)250 (editor-raise "no file name specified")251 fn)))252253;;> Print `objs`, but only if the editor is not in silent mode.254255(define (editor-verbose editor . objs)256 (unless (text-editor-silent? editor)257 (apply println objs)))258259;;> Print `?` optionally followed by `msg`, if the editor is in help mode.260;;> If standard input does not refer to a terminal device, the editor261;;> terminates with a non-zero exit status.262263(define (editor-error editor msg)264 (text-editor-error-set! editor msg)265 (println "?")266 (when (text-editor-help? editor)267 (println msg))268269 ;; See "Consequences of Errors" section in POSIX.1-2008.270 (unless (terminal-port? (current-input-port))271 (exit #f)))272273;;> Raise an R7RS editor error exception with the given `msg`. This274;;> error is caught by an error handler and causes the `msg` to be275;;> printed using the [editor-error](#editor-error) procedure.276277(define (editor-raise msg)278 (raise (make-editor-error msg)))279280;; Editor-Error is a custom object raised to indicate a non-fatal281;; error condition handled according to the ed(1) POSIX specification.282(define-record-type Editor-Error283 (make-editor-error msg)284 editor-error?285 (msg editor-error-msg))286287;;> Reset all file-specific state of the editor.288289(define (editor-reset! editor)290 (text-editor-line-set! editor 0)291 (text-editor-last-line-set! editor 0)292 (text-editor-buffer-set! editor (make-buffer))293 (text-editor-marks-set! editor '()))294295;;> Create an editor mark named `mark` which refers to the given `line`.296297(define (editor-mark-line editor line mark)298 (let ((lines (editor-get-lines editor (cons line line))))299 (if (null? lines)300 (editor-raise "invalid address")301 (text-editor-marks-set! editor302 (alist-cons mark (car lines) (text-editor-marks editor))))))303304(define (editor-get-mark editor mark)305 (let ((pair (assv mark (text-editor-marks editor))))306 (if pair307 (let ((lnum (editor-get-lnum editor (cdr pair))))308 (if lnum309 lnum310 ;; XXX: Delete mark if it is found to be invalid (e.g. line deleted)?311 (editor-raise (string-append "invalid mark: " (string mark)))))312 (editor-raise (string-append "unknown mark: " (string mark))))))313314;;> Move editor cursor to specified line. Line 1 is the first line,315;;> specifying 0 as a line moves the cursor *before* the first line.316317(define (editor-goto! editor line)318 (text-editor-line-set! editor line))319320;; Intermediate range values can be invalid, e.g. "7,5,". This321;; parameter can be set to disable sanity checks on range values.322(define allow-invalid-ranges323 (make-parameter #f))324325;;> Find current line number for a given line in the editor buffer. False326;;> is returned if the line does not exist in the editor buffer.327;;328;; XXX: This implementation assumes that eq? performs pointer comparision,329;; which is the case with CHICKEN but technically this is undefinied behaviour.330331(define (editor-get-lnum editor line)332 (let ((buffer (text-editor-buffer editor)))333 (find334 (lambda (lnum)335 (eq? (buffer-ref buffer (dec lnum)) line))336 (iota (editor-lines editor) 1))))337338;;> Return the content of the editor text buffer as a list of lines339;;> for the specified line pair `lines`. The start address of the340;;> pair is inclusive while the end address is exclusive.341342(define (editor-get-lines editor lines)343 (if (buffer-empty? (text-editor-buffer editor))344 '()345 (let ((sline (car lines))346 (eline (cdr lines)))347 (buffer->list348 (text-editor-buffer editor)349 (max (dec sline) 0)350 eline))))351352;;> Predicate which returns true if the given `line` is within353;;> the range specified by `lines`.354355(define (editor-in-range? editor lines line)356 (let ((sline (car lines))357 (eline (cdr lines)))358 (and (>= line sline) (< line eline))))359360;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;361362;;>| Editor Operations363;;>364;;> Procedure which modify the editor text buffer. Provided operations365;;> are wrappers around operations of the internal text editor366;;> [line buffer][edward buffer] and additionally take care of updating367;;> the editor state (e.g. the [modified state][text-editor-modified?]).368;;>369;;> [text-editor-modified?]: #text-editor-modified?370;;> [edward buffer]: edward.buffer.html371372;; Execute the given thunk and make all buffer operations and editor373;; state modifications performed in thunk undoable via editor-undo!.374375(define (editor-with-undo editor thunk)376 (let ((m? (text-editor-modified? editor))377 (ll (text-editor-line editor)))378 (buffer-with-undo (text-editor-buffer editor) thunk)379380 ;; buffer-with-undo succeeded → commit previous editor state.381 (text-editor-last-modified-set! editor m?)382 (text-editor-last-line-set! editor ll)))383384;;> Undo the last operation on the buffer.385386(define (editor-undo! editor)387 (unless (buffer-has-undo? (text-editor-buffer editor))388 (editor-raise "nothing to undo"))389390 (text-editor-modified-set!391 editor392 (text-editor-last-modified? editor))393394 (let ((cur-line (text-editor-line editor)))395 (text-editor-line-set!396 editor397 (text-editor-last-line editor))398 (text-editor-last-line-set! editor cur-line))399 (buffer-undo! (text-editor-buffer editor)))400401;;> Returns amount of lines in the buffer.402403(define (editor-lines editor)404 (buffer-length (text-editor-buffer editor)))405406;;> Returns list of line numbers for given lines.407408(define (editor-line-numbers lines)409 (let ((sline (car lines))410 (eline (cdr lines)))411 (iota (inc (- eline sline)) sline)))412413;;> Append the text at the current address.414;;> Returns line number of last inserted line.415416(define (editor-append! editor line text)417 (unless (null? text)418 (text-editor-modified-set! editor #t))419420 (let ((buf (text-editor-buffer editor)))421 (buffer-append! buf line text)422 (+ line (length text))))423424;;> Replace text of given lines with given data.425;;> Returns line number of last inserted line.426427(define (editor-replace! editor lines data)428 (text-editor-modified-set! editor #t)429 (let* ((sline (car lines))430 (eline (cdr lines))431 (buffer (text-editor-buffer editor)))432 (buffer-replace! buffer sline eline data)433 (+ (max 0 (dec sline)) (length data))))434435;;> Join given lines to single line. Return value is undefined.436437(define (editor-join! editor lines)438 (text-editor-modified-set! editor #t)439 (let ((sline (car lines))440 (eline (cdr lines))441 (buffer (text-editor-buffer editor)))442 (buffer-join! buffer sline eline)))443444;;> Remove given lines. Return value is undefined.445446(define (editor-remove! editor lines)447 (text-editor-modified-set! editor #t)448 (let ((sline (car lines))449 (eline (cdr lines))450 (buffer (text-editor-buffer editor)))451 (buffer-remove! buffer sline eline)))452453;;> Move given `lines` to given destination `dest-line`.454;;> Returns the address of the last inserted line.455456(define (editor-move! editor lines dest-line)457 (text-editor-modified-set! editor #t)458 (let ((sline (car lines))459 (eline (cdr lines))460 (buffer (text-editor-buffer editor)))461 (buffer-move! buffer sline eline dest-line)462 (min463 (editor-lines editor)464 (let ((diff (- eline sline)))465 (+ dest-line466 ;; If we moved multiple lines, we need to increment467 ;; the destination lines by the amount of lines moved.468 (if (zero? diff) diff (inc diff)))))))469470;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;471472;;>| Address Translation473;;>474;;> Procedures for performing address translation. That is, procedures475;;> which convert an [edward ed addr][edward ed addr] to a line number476;;> (or a pair of line numbers) based on the current editor state. For477;;> example, the ed address `.` would be converted to the current line478;;> number (as tracked in the [editor object][section text-editor]).479;;> The resulting address can then be passed to an480;;> [editor operation][section operations].481;;>482;;> [edward ed addr]: edward.ed.addr.html483;;> [section text-editor]: #section-text-editor-object484;;> [section operations]: #section-editor-operations485486;;> Convert a single address (i.e. as created via [make-addr][make-addr])487;;> to a single line number. This is a procedure which must be passed the488;;> text editor object and an edward ed address as procedure arguments.489;;>490;;> [make-addr]: edward.ed.addr.html#make-addr491492(define addr->line493 (match-lambda*494 ((e ('(current-line) off))495 (%addr->line e off (text-editor-line e)))496 ((e ('(last-line) off))497 (%addr->line e off (editor-lines e)))498 ((e (('nth-line . line) off))499 (%addr->line e off line))500 ((e (('marked-line . mark) off))501 (%addr->line e off (editor-get-mark e mark)))502 ((e (('regex-forward . bre) off))503 (%addr->line e off (match-line 'forward e bre)))504 ((e (('regex-backward . bre) off))505 (%addr->line e off (match-line 'backward e bre)))506 ((e (('relative . rel) off))507 (%addr->line e off (+ (text-editor-line e) rel)))))508509(define (%addr->line editor off line)510 (let* ((total-off (apply + off))511 (nline (+ total-off line)))512 (if (or513 (> 0 nline)514 (> nline (editor-lines editor)))515 (editor-raise (string-append "invalid final address value: "516 (number->string nline)))517 nline)))518519(define (match-line direction editor bre)520 (define (next-index idx)521 (modulo522 (case direction523 ((forward) (inc idx))524 ((backward) (dec idx)))525 (editor-lines editor)))526527 (when (zero? (editor-lines editor))528 (editor-raise "no match"))529 (let* ((buffer (text-editor-buffer editor))530 (regex (editor-make-regex editor bre))531 (start-idx (max (dec (text-editor-line editor)) 0)))532 (let lp ((idx (next-index start-idx)))533 (if (regex-match? regex (buffer-ref buffer idx))534 (inc idx) ;; convert index back to line number535 (if (eqv? idx start-idx)536 (editor-raise "no match")537 (lp (next-index idx)))))))538539;;> Convert a `range` address (i.e. as created via [make-range][make-range])540;;> to a line pair. This procedure does not modify the current editor541;;> addresses, even for address range like `5;6`.542;;>543;;> [make-range]: edward.ed.addr.html#make-range544545(define (range->lpair editor range)546 (let* ((cur (make-addr '(current-line)))547 (old (addr->line editor cur))548 (ret (range->lpair! editor range)))549 (editor-goto! editor old) ;; undo range->lpair! side-effect550 ret))551552(define (range->lpair! editor range)553 (define (%range->lpair! editor start end)554 (let ((sline (addr->line editor start))555 (eline (addr->line editor end)))556 (if (and (not (allow-invalid-ranges))557 (> sline eline))558 (editor-raise "invalid range specification")559 (cons sline eline))))560561 ;; In the case of a <semicolon> separator, the current line ('.') shall562 ;; be set to the first address, and only then will the second address be563 ;; calculated. This feature can be used to determine the starting line564 ;; for forwards and backwards searches.565 (match range566 ((fst #\; snd)567 (editor-goto! editor (addr->line editor fst)) ;; side-effect568 (%range->lpair! editor (make-addr '(current-line)) snd))569 ((fst #\, snd)570 (%range->lpair! editor fst snd))))571572;; This procedure expands a list of addresses into a single pair of573;; concrete line numbers. As such, this procedure is responsible for574;; both applying the omission rules and discarding addresses.575;;576;; For example the address list for "7,5," is evaluted as follows:577;;578;; 7,5, [discard] -> 5, [expand] -> 5,5579;;580(define (%addrlst->lpair editor lst)581 (range->lpair!582 editor583 (expand-addr584 (fold (lambda (cur stk)585 (if (and (address-separator? cur)586 (any address-separator? stk))587 ;; Intermediate range values can be invalid (e.g. "7,5,").588 (let ((lpair (parameterize ((allow-invalid-ranges #t))589 (range->lpair! editor (expand-addr stk)))))590 (list591 (make-addr (cons 'nth-line (cdr lpair))) ;; discard car592 cur))593 (append stk (list cur))))594 '() lst))))595596;;> This procedure takes an addrlist, as returned by [parse-addrs][parse-addrs]597;;> and an editor object as an argument and returns a concrete line pair598;;> for this address. This line pair can then be passed to defined599;;> editor commands.600;;>601;;> [parse-addrs]: edward.ed.addr.html#parse-addrs602603(define (addrlst->lpair editor lst)604 (let* ((cur (make-addr '(current-line)))605 (old (addr->line editor cur))606 (ret (%addrlst->lpair editor lst)))607 (editor-goto! editor old) ;; undo range->lpair! side-effect608 ret))609610;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;611612;;>| Command Execution613;;>614;;> Based on defined [editor operations][section operations], it is615;;> possible to define custom editor commands which combine multiple616;;> operations. These commands can then be executed by the users via617;;> the REPL spawned by [editor-start][editor-start].618;;>619;;> **Warning:** The procedures documented here provide a low-level620;;> interface for defining custom editor commands. However, it is highly621;;> discouraged to use this interface directly. Instead, editor commands622;;> should be defined through the macros provided by [edward ed623;;> cmd][edward ed cmd].624;;>625;;> [editor-start]: #editor-start626;;> [section operations]: #section-editor-operations627;;> [edward ed cmd]: edward.ed.cmd.html628629;;> Record type representing editor commands as defined in POSIX.1-2008.630631(define-record-type Editor-Command632 ;;> Create a new editor command. The command is identified by a unique633 ;;> `symbol` and procedure `proc` which receives an editor object and634 ;;> the given `args` as procedure arguments.635 (make-cmd symbol default-addr proc args)636 ;;> Predicate which returns true if the given `obj` was created using637 ;;> the [make-cmd](#make-cmd) procedure.638 editor-cmd?639 (default-addr cmd-default-addr)640 (symbol cmd-symbol)641 (proc cmd-proc)642 (args643 ;;> Retrieve additional arguments defined for this command.644 ;;> The returned list value does not include the editor object.645 cmd-args))646647;; Returns true if the given command is a command which is reversible648;; according to the definition of the undo command in POSIX. For the649;; undo command itself, #f is returned.650651(define (cmd-reversible? cmd)652 (member (cmd-symbol cmd)653 '(append change delete global insert join move654 read substitute copy global-unmatched interactive655 interactive-unmatched)))656657;;> Execute an editor command `cmd` using the given `editor` state658;;> on the addresses given by `addrlst`. The given addresses are659;;> translated to line addresses internally. If a command should660;;> be executed on a line address directly, use the661;;> [editor-xexec](#editor-xexec) procedure instead.662663(define (editor-exec editor addrlst cmd)664 ;; XXX: Special handling for write command with empty buffer.665 ;; Without this, it would be impossible to use write with666 ;; an empty buffer since the default address is invalid then.667 ;;668 ;; TODO: Find a better way to deal with this edge case.669 (if (and (eqv? (cmd-symbol cmd) 'write)670 (not addrlst)671 (buffer-empty? (text-editor-buffer editor)))672 (editor-xexec editor '(1 . 1) cmd)673 (let* ((default-addr (cmd-default-addr cmd))674 ;; Convert addrlst to line pair (if any given) or675 ;; use default address and convert that (if any).676 (line-pair (if addrlst677 (addrlst->lpair editor addrlst)678 (and (not (null? default-addr))679 (range->lpair editor (addr->range default-addr)))))680 ;; Convert given address (if any) to a single line681 ;; or a line pair (depending on default address).682 (line-addr (if (or (not line-pair) (range? default-addr))683 line-pair684 (cdr line-pair))))685 (editor-xexec editor line-addr cmd))))686687;;> Execute given `cmd` using given `editor` state on the address688;;> `addr`. The address can either be a single line address, a689;;> line pair, or an empty list depending on the default address690;;> specified for `cmd`. If the command doesn't specify a default691;;> address (i.e. doesn't expect an address argument) then it is692;;> an error to pass anything other than the empty list as an693;;> `addr` value to this procedure.694695(define (editor-xexec editor addr cmd)696 (let ((default-addr (cmd-default-addr cmd)))697 (when (and (null? default-addr) addr)698 (editor-raise "unexpected address"))699 (when (and (range? default-addr) (zero? (car addr)))700 (editor-raise "ranges cannot start at address zero"))701702 (apply (cmd-proc cmd)703 editor704 (if (null? (cmd-default-addr cmd)) ;; doesn't expect address705 (cmd-args cmd)706 (append (list addr) (cmd-args cmd))))))707708;;> Execute a list of commands using given editor state.709710(define (editor-exec-cmdlist editor cmd-pairs)711 (for-each (lambda (cmd-pair)712 (editor-exec editor (car cmd-pair) (cdr cmd-pair)))713 cmd-pairs))