ed

[hare] The standard editor
Log | Files | Refs | README | LICENSE

commit d63c3dd4a73f41d3a2bd76ea18638ca38eb67428
parent 307231e7325386b4c74130c8d4f65eb44bdd6776
Author: Byron Torres <b@torresjrjr.com>
Date:   Mon, 15 Jan 2024 22:06:08 +0000

cmd_substitute(): impl multiline replacement text

Diffstat:
Mcommand.ha | 32++++++++++++++++++++------------
Mparse.ha | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
2 files changed, 78 insertions(+), 25 deletions(-)

diff --git a/command.ha b/command.ha @@ -14,6 +14,7 @@ type Command = struct{ name: rune, suffix: rune, printmode: PrintMode, + delim: rune, arg1: str, arg2: str, arg3: str, @@ -472,12 +473,13 @@ fn cmd_substitute(s: *Session, cmd: *Command) (void | Error) = { )?; assert_nonzero(s, a)?; - const re = newregex(s, cmd.arg1)?; defer regex::finish(&re); - const replacement = cmd.arg2; - // TODO: parse and use substitution flags `cmd.arg3` - // use regex::replace ? // TODO: implement '&', '%' - let count = if (cmd.count == 0z) 0z else cmd.count - 1z; + + const re = newregex(s, cmd.arg1)?; + defer regex::finish(&re); + const replacement = strings::join("\n", cmd.textinput...); + defer free(replacement); + const count = if (cmd.count == 0z) 0z else cmd.count - 1z; for (let n = a; n <= b; n += 1) { const old = s.buf.lines[n].text; @@ -504,16 +506,22 @@ fn cmd_substitute(s: *Session, cmd: *Command) (void | Error) = { break; }; memio::concat(&new, strings::sub(old, rbound, strings::end))!; - - let newline = alloc(Line{ - text = strings::dup(memio::string(&new)!), - ... - }); + let newtext = memio::string(&new)!; + let newtexts = strings::split(newtext, "\n"); buf_delete(s.buf, n, n); - buf_insert(s.buf, n, newline); - s.buf.cursor = n; + for (let i = 0z; i < len(newtexts); i += 1) { + let newtext = newtexts[i]; + let newline = alloc(Line{ + text = strings::dup(newtext), + ... + }); + + buf_insert(s.buf, n + i, newline); + + s.buf.cursor = n + i; + }; }; printmode(s, cmd)?; diff --git a/parse.ha b/parse.ha @@ -45,7 +45,7 @@ fn parse(input: *bufio::scanner) (Command | ParseError | InteractionError) = { cmd.addrs = scan_addrs(&iter); cmd.name = scan_cmdname(&iter)?; - parse_cmdargs(&cmd, &iter)?; + let ready = parse_cmdargs(&cmd, &iter)?; switch :input (cmd.name) { case => void; @@ -91,6 +91,44 @@ fn parse(input: *bufio::scanner) (Command | ParseError | InteractionError) = { append(cmd.textinput, strings::dup(strings::rtrim(inputline, '\\'))); }; + case 's' => + if (ready) + yield; + + for (true) { + let inputline = match (bufio::scan_line(input)?) { + case let s: const str => + yield s; + case io::EOF => + break; + }; + // workaround scan_line undelimited last line. + // note, GNU ed would use "p\n" instead. + if (inputline == "") { + append(cmd.textinput, "\n"); + break; + }; + + let iter = strings::iter(inputline); + let (part, seen_delim) = scan_item(&iter, cmd.delim); + + // cmd.textinput holds the replacement text + append(cmd.textinput, strings::dup(part)); + + if (!seen_delim && strings::hassuffix(inputline, '\\')) + continue; + + strings::next(&iter); // skip delim + let (count, global, printmode) = scan_substitute_flags(&iter); + //debug("count={} global={}", count, global); + cmd.count = count; + cmd.flag_global = global; + cmd.printmode = printmode; + + scan_end_assert(&iter)?; + + break; + }; }; for (let i = 0z; i < len(cmd.textinput); i += 1) @@ -159,7 +197,7 @@ fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = // ./<regex>[/] where delimiter '/' is arbitrary case 'G', 'V' => - const delim = match (strings::next(iter)) { + cmd.delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -169,14 +207,14 @@ fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = yield r; }; }; - cmd.arg1 = scan_item(iter, delim); + cmd.arg1 = scan_item(iter, cmd.delim).0; strings::next(iter); // scan delimiter if exists scan_end_assert(iter)?; return true; // ./<regex>/<cmdlist...> case 'g', 'v' => - const delim = match (strings::next(iter)) { + cmd.delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -186,7 +224,7 @@ fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = yield r; }; }; - cmd.arg1 = scan_item(iter, delim); + cmd.arg1 = scan_item(iter, cmd.delim).0; strings::next(iter); // scan delimiter if exists cmd.arg2 = scan_rest(iter); // TODO: append to .subcmds? if (strings::prev(iter) as rune == '\\') { @@ -196,7 +234,7 @@ fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = // s/<regex>/[<replace>[/[<flags>]]] case 's' => - const delim = match (strings::next(iter)) { + cmd.delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -206,22 +244,26 @@ fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = yield r; }; }; - cmd.arg1 = scan_item(iter, delim); + cmd.arg1 = scan_item(iter, cmd.delim).0; match (strings::next(iter)) { case rune => void; case void => return ExpectedDelimiter; }; - cmd.arg2 = scan_item(iter, delim); + append(cmd.textinput, scan_item(iter, cmd.delim).0); match (strings::next(iter)) { case rune => void; case void => - return true; + if (strings::prev(iter) == '\\') + return false + else + return true; }; let (count, global, printmode) = scan_substitute_flags(iter); cmd.count = count; cmd.flag_global = global; cmd.printmode = printmode; + scan_end_assert(iter)?; return true; case '&' => @@ -330,14 +372,14 @@ fn scan_addr(iter: *strings::iterator) (Address | void) = { yield scan_mark(iter); case '/' => const rad = RegexAddr{ - expr = scan_item(iter, '/'), + expr = scan_item(iter, '/').0, direction = true, }; strings::next(iter); yield rad; case '?' => const rad = RegexAddr{ - expr = scan_item(iter, '?'), + expr = scan_item(iter, '?').0, direction = false, }; strings::next(iter); @@ -468,8 +510,9 @@ fn scan_rest(iter: *strings::iterator) str = { return strings::trim(strings::fromrunes(rs)); }; -fn scan_item(iter: *strings::iterator, delim: rune) str = { +fn scan_item(iter: *strings::iterator, delim: rune) (str, bool) = { let rs: []rune = []; + let seen_delim = false; for (true) { let r = match (strings::next(iter)) { case void => @@ -483,6 +526,7 @@ fn scan_item(iter: *strings::iterator, delim: rune) str = { break; // TODO: Error here? how? case let r: rune => if (r == delim) { + seen_delim = true; append(rs, r); } else { append(rs, ['\\', r]...); @@ -490,12 +534,13 @@ fn scan_item(iter: *strings::iterator, delim: rune) str = { continue; }; } else if (r == delim) { + seen_delim = true; strings::prev(iter); break; }; append(rs, r); }; - return strings::fromrunes(rs); + return (strings::fromrunes(rs), seen_delim); }; fn scan_mark(iter: *strings::iterator) rune = {