1%%%---------------------------------------------------------------------- 2%%% File : eed.erl 3%%% Author : Bjorn Gustavsson <bjorn@strider> 4%%% Purpose : Unix `ed' look-alike. 5%%% Created : 24 Aug 1997 by Bjorn Gustavsson <bjorn@strider> 6%%%---------------------------------------------------------------------- 7 8-module(eed). 9-author('bjorn@strider'). 10 11-export([edit/0, edit/1, file/1, cmd_line/1]). 12 13-compile({no_auto_import,[error/1]}). 14 15-record(state, {dot = 0, % Line number of dot. 16 upto_dot = [], % Lines up to dot (reversed). 17 after_dot = [], % Lines after dot. 18 lines = 0, % Total number of lines. 19 print=false, % Print after command. 20 filename=[], % Current file. 21 pattern, % Current pattern. 22 in_global=false, % True if executing global command. 23 input=[], % Global input stream. 24 undo, % Last undo state. 25 marks=[], % List of marks. 26 modified=false, % Buffer is modified. 27 opts=[{prompt, ''}], % Options. 28 last_error, % The last error encountered. 29 input_fd % Input file descriptor. 30 }). 31 32-record(line, {contents, % Contents of line. 33 mark=false % Marked (for global prefix). 34 }). 35 36cmd_line([Script]) -> 37 file(Script), 38 halt(). 39 40file(Script) -> 41 case file:open(Script, [read]) of 42 {ok,Fd} -> 43 loop(#state{input_fd=Fd}), 44 ok; 45 {error,E} -> 46 {error,E} 47 end. 48 49edit() -> 50 loop(#state{input_fd=group_leader()}). 51 52edit(Name) -> 53 loop(command([$e|Name], #state{input_fd=group_leader()})). 54 55loop(St0) -> 56 {ok, St1, Cmd} = get_line(St0), 57 case catch command(nonl(Cmd), St1) of 58 {'EXIT', Reason} -> 59 %% XXX Should clear outstanding global command here. 60 loop(print_error({'EXIT', Reason}, St1)); 61 quit -> 62 ok; 63 {error, Reason} -> 64 loop(print_error(Reason, St1)); 65 St2 when is_record(St2, state) -> 66 loop(St2) 67 end. 68 69nonl([$\n]) -> []; 70nonl([]) -> []; 71nonl([H|T]) -> [H|nonl(T)]. 72 73command(Cmd, St) -> 74 case parse_command(Cmd, St) of 75 quit -> 76 quit; 77 St1 when is_function(St1#state.print) -> 78 if 79 St1#state.dot /= 0 -> 80 print_current(St1); 81 true -> 82 ok 83 end, 84 St1#state{print=false}; 85 St1 when is_record(St1, state) -> 86 St1 87 end. 88 89get_line(St) -> 90 Opts = St#state.opts, 91 {value, {prompt, Prompt}} = lists:keysearch(prompt, 1, Opts), 92 get_line(Prompt, St). 93 94get_line(Prompt, St) when St#state.input == [] -> 95 Line = get_line1(St#state.input_fd, Prompt, []), 96 {ok, St, Line}; 97get_line(_, St) -> 98 get_input(St#state.input, St, []). 99 100get_input([eof], St, []) -> 101 {ok, St, eof}; 102get_input([eof], St, Result) -> 103 {ok, St#state{input=[eof]}, lists:reverse(Result)}; 104get_input([$\n|Rest], St, Result) -> 105 {ok, St#state{input=Rest}, lists:reverse(Result)}; 106get_input([C|Rest], St, Result) -> 107 get_input(Rest, St, [C|Result]). 108 109get_line1(Io, Prompt, Result) -> 110 get_line2(Io, io:get_line(Io, Prompt), Result). 111 112get_line2(_Io, eof, []) -> 113 eof; 114get_line2(_Io, eof, Result) -> 115 lists:reverse(Result); 116get_line2(Io, [$\\, $\n], Result) -> 117 get_line1(Io, '', [$\n|Result]); 118get_line2(_Io, [$\n], Result) -> 119 lists:reverse(Result, [$\n]); 120get_line2(Io, [C|Rest], Result) -> 121 get_line2(Io, Rest, [C|Result]). 122 123print_error(Reason, St0) -> 124 St1 = St0#state{last_error=Reason}, 125 io:put_chars("?\n"), 126 case lists:member(help_always, St1#state.opts) of 127 true -> 128 help_command([], [], St1), 129 St1; 130 false -> 131 St1 132 end. 133 134format_error(bad_command) -> "unknown command"; 135format_error(bad_filename) -> "illegal or missing filename"; 136format_error(bad_file) -> "cannot open input file"; 137format_error(bad_linenum) -> "line out of range"; 138format_error(bad_delimiter) -> "illegal or missing delimiter"; 139format_error(bad_undo) -> "nothing to undo"; 140format_error(bad_mark) -> "mark not lower case ascii"; 141format_error(bad_pattern) -> "invalid regular expression"; 142format_error(buffer_modified) -> "warning: expecting `w'"; 143format_error(nested_globals) -> "multiple globals not allowed"; 144format_error(nomatch) -> "search string not found"; 145format_error(missing_space) -> "no space after command"; 146format_error(garbage_after_command) -> "illegal suffix"; 147format_error(not_implemented) -> "not implemented yet"; 148format_error({'EXIT', {Code, {Mod, Func, Args}}}) -> 149 lists:flatten(io_lib:format("aborted due to bug (~p)", 150 [{Code, {Mod, Func, length(Args)}}])); 151format_error(A) -> atom_to_list(A). 152 153 154 155%%% Parsing commands. 156 157parse_command(Cmd, St) -> 158 parse_command(Cmd, St, []). 159 160parse_command(Cmd, State, Nums) -> 161 case get_one(Cmd, State) of 162 {ok, Num, Rest, NewState} -> 163 parse_next_address(Rest, NewState, [Num|Nums]); 164 false -> 165 parse_command1(Cmd, State, Nums) 166 end. 167 168parse_next_address([$,|Rest], State, Nums) -> 169 parse_command(Rest, State, Nums); 170parse_next_address([$;|Rest], State, [Num|Nums]) -> 171 parse_command(Rest, move_to(Num, State), [Num|Nums]); 172parse_next_address(Rest, State, Nums) -> 173 parse_command1(Rest, State, Nums). 174 175parse_command1([Letter|Rest], State, Nums) -> 176 Cont = fun(Fun, NumLines, Def) -> 177 execute_command(Fun, NumLines, Def, State, Nums, Rest) end, 178 parse_cmd_char(Letter, Cont); 179parse_command1([], State, Nums) -> 180 execute_command(fun print_command/3, 1, next, State, Nums, []). 181 182get_one(Cmd, St) -> 183 case get_address(Cmd, St) of 184 {ok, Addr, Cmd1, St1} -> 185 get_one1(Cmd1, Addr, St1); 186 false -> 187 get_one1(Cmd, false, St) 188 end. 189 190get_one1([D|Rest], false, St) when $0 =< D, D =< $9 -> 191 get_one2(get_number([D|Rest]), 1, 0, St); 192get_one1([D|Rest], Sum, St) when $0 =< D, D =< $9 -> 193 get_one2(get_number([D|Rest]), 1, Sum, St); 194get_one1([$+, D|Rest], Sum, St) when $0 =< D, D =< $9 -> 195 get_one2(get_number([D|Rest]), 1, Sum, St); 196get_one1([$-, D|Rest], Sum, St) when $0 =< D, D =< $9 -> 197 get_one2(get_number([D|Rest]), -1, Sum, St); 198get_one1([$+|Rest], Sum, St) -> 199 get_one2({ok, 1, Rest}, 1, Sum, St); 200get_one1([$-|Rest], Sum, St) -> 201 get_one2({ok, 1, Rest}, -1, Sum, St); 202get_one1(_Cmd, false, _St) -> 203 false; 204get_one1(Cmd, Sum, St) -> 205 {ok, Sum, Cmd, St}. 206 207get_one2({ok, Number, Rest}, Mul, false, St) -> 208 get_one1(Rest, St#state.dot+Mul*Number, St); 209get_one2({ok, Number, Rest}, Mul, Sum, St) -> 210 get_one1(Rest, Sum+Mul*Number, St). 211 212get_number(Cmd) -> 213 get_number(Cmd, 0). 214 215get_number([D|Rest], Result) when $0 =< D, D =< $9 -> 216 get_number(Rest, Result*10+D-$0); 217get_number(Rest, Result) -> 218 {ok, Result, Rest}. 219 220get_address([$.|Rest], State) -> 221 {ok, State#state.dot, Rest, State}; 222get_address([$$|Rest], State) -> 223 {ok, State#state.lines, Rest, State}; 224get_address([$', Mark|Rest], St) when $a =< Mark, Mark =< $z -> 225 case lists:keysearch(Mark, 2, St#state.marks) of 226 {value, {Line, Mark}} -> 227 {ok, Line, Rest, St}; 228 false -> 229 {ok, 0, Rest, St} 230 end; 231get_address([$'|_Rest], _State) -> 232 error(bad_mark); 233get_address([$/|Rest], State) -> 234 scan_forward($/, Rest, State); 235get_address([$?|_Rest], _State) -> 236 error(not_implemented); 237get_address(_Cmd, _St) -> 238 false. 239 240scan_forward(End, Patt0, State) -> 241 {ok, Rest, NewState} = get_pattern(End, Patt0, State), 242 Dot = NewState#state.dot, 243 After = NewState#state.after_dot, 244 scan_forward1(Dot+1, After, NewState, Rest). 245 246scan_forward1(Linenum, [Line|Rest], State, RestCmd) -> 247 case re:run(Line#line.contents, State#state.pattern, [{capture, none}]) of 248 match -> 249 {ok, Linenum, RestCmd, State}; 250 nomatch -> 251 scan_forward1(Linenum+1, Rest, State, RestCmd) 252 end; 253scan_forward1(_, [], State, RestCmd) -> 254 Dot = State#state.dot, 255 Upto = State#state.upto_dot, 256 case scan_forward2(Dot, Upto, State, RestCmd) of 257 false -> 258 error(bad_linenum); 259 Other -> 260 Other 261 end. 262 263scan_forward2(0, [], _State, _RestCmd) -> 264 false; 265scan_forward2(Linenum, [Line|Rest], State, RestCmd) -> 266 case scan_forward2(Linenum-1, Rest, State, RestCmd) of 267 false -> 268 case re:run(Line#line.contents, State#state.pattern, 269 [{capture, none}]) of 270 match -> 271 {ok, Linenum, RestCmd, State}; 272 nomatch -> 273 false 274 end; 275 Other -> 276 Other 277 end. 278 279parse_cmd_char($S, Cont) -> Cont(fun quest_command/3, 0, none); 280parse_cmd_char($T, Cont) -> Cont(fun time_command/3, 0, none); 281parse_cmd_char($=, Cont) -> Cont(fun print_linenum/3, 1, last); 282parse_cmd_char($a, Cont) -> Cont(fun append_command/3, 1, dot); 283parse_cmd_char($c, Cont) -> Cont(fun change_command/3, 2, dot); 284parse_cmd_char($d, Cont) -> Cont(fun delete_command/3, 2, dot); 285parse_cmd_char($e, Cont) -> Cont(fun enter_command/3, 0, none); 286parse_cmd_char($E, Cont) -> Cont(fun enter_always_command/3, 0, none); 287parse_cmd_char($f, Cont) -> Cont(fun file_command/3, 0, none); 288parse_cmd_char($g, Cont) -> Cont(fun global_command/3, 2, all); 289parse_cmd_char($h, Cont) -> Cont(fun help_command/3, 0, none); 290parse_cmd_char($H, Cont) -> Cont(fun help_always_command/3, 0, none); 291parse_cmd_char($i, Cont) -> Cont(fun insert_command/3, 1, dot); 292parse_cmd_char($k, Cont) -> Cont(fun mark_command/3, 1, dot); 293parse_cmd_char($l, Cont) -> Cont(fun list_command/3, 2, dot); 294parse_cmd_char($m, Cont) -> Cont(fun move_command/3, 2, dot); 295parse_cmd_char($n, Cont) -> Cont(fun number_command/3, 2, dot); 296parse_cmd_char($p, Cont) -> Cont(fun print_command/3, 2, dot); 297parse_cmd_char($P, Cont) -> Cont(fun prompt_command/3, 0, none); 298parse_cmd_char($q, Cont) -> Cont(fun quit_command/3, 0, none); 299parse_cmd_char($Q, Cont) -> Cont(fun quit_always_command/3, 0, none); 300parse_cmd_char($r, Cont) -> Cont(fun read_command/3, 1, last); 301parse_cmd_char($s, Cont) -> Cont(fun subst_command/3, 2, dot); 302parse_cmd_char($t, Cont) -> Cont(fun transpose_command/3, 2, dot); 303parse_cmd_char($u, Cont) -> Cont(fun undo_command/3, 0, none); 304parse_cmd_char($v, Cont) -> Cont(fun vglobal_command/3, 2, all); 305parse_cmd_char($w, Cont) -> Cont(fun write_command/3, 2, all); 306parse_cmd_char(_, _Cont) -> error(bad_command). 307 308execute_command(Fun, NumLines, Def, State, Nums, Rest) -> 309 Lines = check_lines(NumLines, Def, Nums, State), 310 Fun(Rest, Lines, State). 311 312check_lines(0, _, [], _State) -> 313 []; 314check_lines(1, dot, [], #state{dot=Dot}) -> 315 [Dot]; 316check_lines(1, next, [], State) when State#state.dot < State#state.lines -> 317 [State#state.dot+1]; 318check_lines(1, last, [], State) -> 319 [State#state.lines]; 320check_lines(1, _, [Num|_], State) when 0 =< Num, Num =< State#state.lines -> 321 [Num]; 322check_lines(2, dot, [], #state{dot=Dot}) -> 323 [Dot, Dot]; 324check_lines(2, all, [], #state{lines=Lines}) -> 325 [1, Lines]; 326check_lines(2, _, [Num], State) when 0 =< Num, Num =< State#state.lines -> 327 [Num, Num]; 328check_lines(2, _, [Num2, Num1|_], State) 329when 0 =< Num1, Num1 =< Num2, Num2 =< State#state.lines -> 330 [Num1, Num2]; 331check_lines(_, _, _, _) -> 332 error(bad_linenum). 333 334 335%%% Executing commands. 336 337%% ($)= - print line number 338 339print_linenum(Rest, [Line], State) -> 340 NewState = check_trailing_p(Rest, State), 341 io:format("~w\n", [Line]), 342 NewState. 343 344%% ? - print state (for debugging) 345 346quest_command([], [], State) -> 347 io:format("~p\n", [State]), 348 State. 349 350%% Tcmd - time command 351 352time_command(Cmd, [], St) -> 353 Fun = fun parse_command/2, 354 erlang:garbage_collect(), 355 {Elapsed, Val} = timer:tc(erlang, apply, [Fun, [Cmd, St]]), 356 io:format("Time used: ~p s~n", [Elapsed/1000000.0]), 357 case Val of 358 {error, Reason} -> 359 throw({error, Reason}); 360 Other -> 361 Other 362 end. 363 364%% (.)a - append text 365 366append_command(Rest, [Line], St0) -> 367 St1 = save_for_undo(St0), 368 append(move_to(Line, check_trailing_p(Rest, St1))). 369 370append(St0) -> 371 {ok, St1, Line0} = get_line('', St0), 372 case Line0 of 373 eof -> 374 St1; 375 ".\n" -> 376 St1; 377 Line -> 378 append(insert_line(Line, St1)) 379 end. 380 381%% (.,.)c 382 383change_command(Rest, Lines, St0) -> 384 St1 = delete_command(Rest, Lines, St0), 385 St2 = append_command([], [St1#state.dot-1], St1), 386 save_for_undo(St2, St0). 387 388%% (.,.)d - delete lines 389 390delete_command(_Rest, [0, _Last], _St) -> 391 error(bad_linenum); 392delete_command(Rest, [First, Last], St0) -> 393 St1 = check_trailing_p(Rest, save_for_undo(St0)), 394 delete(Last-First+1, move_to(Last, St1)). 395 396delete(0, St) when St#state.dot == St#state.lines -> 397 St; 398delete(0, St) -> 399 next_line(St); 400delete(Left, St0) -> 401 St1 = delete_current_line(St0), 402 delete(Left-1, St1). 403 404%% e file - replace buffer with new file 405 406enter_command(_Name, [], St) when St#state.modified == true -> 407 error(buffer_modified); 408enter_command(Name, [], St0) -> 409 enter_always_command(Name, [], St0). 410 411%% E file - replace buffer with new file 412 413enter_always_command(Name, [], St0) -> 414 St1 = read_command(Name, [0], #state{filename=St0#state.filename, 415 opts=St0#state.opts}), 416 St1#state{modified=false}. 417 418%% f file - print filename; set filename 419 420file_command([], [], St) -> 421 io:format("~s~n", [St#state.filename]), 422 St; 423file_command([$_|Name0], [], St) -> 424 Name = skip_blanks(Name0), 425 file_command([], [], St#state{filename=Name}); 426file_command(_, _, _) -> 427 error(missing_space). 428 429%% (1,$)g/RE/commands - execute commands on all matching lines. 430%% (1,$)v/RE/commands - execute commands on all non-matching lines. 431 432global_command(Cmd, Lines, St) -> 433 check_global0(true, Cmd, Lines, St). 434 435vglobal_command(Cmd, Lines, St) -> 436 check_global0(false, Cmd, Lines, St). 437 438check_global0(_, _, _, St) when St#state.in_global == true -> 439 error(nested_globals); 440check_global0(Sense, [Sep|Pattern], Lines, St0) -> 441 {ok, Cmd, St1} = get_pattern(Sep, Pattern, St0), 442 St2 = mark(Sense, Lines, St1), 443 do_global_command(Cmd, St2#state{in_global=true}, 0). 444 445mark(Sense, [First, Last], St0) -> 446 St1 = move_to(Last, St0), 447 mark1(Sense, First-1, St1). 448 449mark1(_Sense, First, St) when St#state.dot == First -> 450 St; 451mark1(Sense, First, St) -> 452 [Line|Prev] = St#state.upto_dot, 453 NewLine = case match(St) of 454 true -> Line#line{mark=Sense}; 455 false -> Line#line{mark=not(Sense)} 456 end, 457 mark1(Sense, First, prev_line(St#state{upto_dot=[NewLine|Prev]})). 458 459do_global_command(Cmd, St0, Matches) -> 460 case find_mark(St0) of 461 {ok, St1} -> 462 St2 = St1#state{input=Cmd++[eof]}, 463 {ok, St3, Cmd1} = get_line(St2), 464 St4 = command(Cmd1, St3), 465 %% XXX There might be several commands. 466 do_global_command(Cmd, St4, Matches+1); 467 false when Matches == 0 -> 468 error(nomatch); 469 false -> 470 St0#state{in_global=false, input=[]} 471 end. 472 473find_mark(State) -> 474 find_mark(State#state.lines, State). 475 476find_mark(0, _State) -> 477 false; 478find_mark(Limit, State) when State#state.dot == 0 -> 479 find_mark(Limit, next_line(State)); 480find_mark(Limit, State) -> 481 case State#state.upto_dot of 482 [Line|Prev] when Line#line.mark == true -> 483 NewLine = Line#line{mark=false}, 484 {ok, State#state{upto_dot=[NewLine|Prev]}}; 485 _Other -> 486 find_mark(Limit-1, wrap_next_line(State)) 487 end. 488 489%% h - print info about last error 490 491help_command([], [], St) -> 492 case St#state.last_error of 493 undefined -> 494 St; 495 Reason -> 496 io:put_chars(format_error(Reason)), 497 io:nl(), 498 St 499 end; 500help_command(_, _, _) -> 501 error(garbage_after_command). 502 503%% H - toggle automatic help mode on/off 504 505help_always_command([], [], St) -> 506 Opts = St#state.opts, 507 case lists:member(help_always, Opts) of 508 true -> 509 St#state{opts=Opts--[help_always]}; 510 false -> 511 help_command([], [], St), 512 St#state{opts=[help_always|Opts]} 513 end. 514 515%% (.)i - insert text 516 517insert_command(_Rest, [0], _State) -> 518 error(bad_linenum); 519insert_command(Rest, [Line], State) -> 520 append_command(Rest, [Line-1], State). 521 522%% (.)kx - mark line 523 524mark_command(_, [0], _St) -> 525 error(bad_linenum); 526mark_command([Mark|_Rest], [_Line], _St) when $a =< Mark, Mark =< $z -> 527 error(not_implemented); 528mark_command(_, _, _) -> 529 error(bad_mark). 530 531%% (.,.)l - list lines 532 533list_command(Rest, Lines, St) -> 534 print([$l|Rest], Lines, St). 535 536%% (.,.)m - move lines 537 538move_command(_Cmd, [_First, _Last], _St) -> 539 error(not_implemented). 540 541%% (.,.)t - copy lines 542 543transpose_command(_Cmd, [_First, _Last], _St) -> 544 error(not_implemented). 545 546%% (.,.)n - print lines with line numbers 547 548number_command(Rest, Lines, St) -> 549 print([$n|Rest], Lines, St). 550 551%% (.,.)p - print lines 552 553print_command(Rest, Lines, St) -> 554 print([$p|Rest], Lines, St). 555 556%% P - toggle prompt 557 558prompt_command([], [], St) -> 559 Opts = St#state.opts, 560 case lists:keysearch(prompt, 1, Opts) of 561 {value, {prompt, ''}} -> 562 St#state{opts=[{prompt, '*'}|Opts]}; 563 {value, Value} -> 564 St#state{opts=[{prompt, ''} | Opts--[Value]]} 565 end; 566prompt_command(_, _, _) -> 567 error(garbage_after_command). 568 569%% q - quit editor 570 571quit_command([], [], _) -> 572 quit; 573quit_command(_, _, _) -> 574 error(garbage_after_command). 575 576%% Q - quit editor 577 578quit_always_command([], [], _) -> 579 quit; 580quit_always_command(_, _, _) -> 581 error(garbage_after_command). 582 583%% ($)r file - read file 584 585read_command([], _, St) when St#state.filename == [] -> 586 error(bad_filename); 587read_command([], [After], St) -> 588 read(After, St#state.filename, St); 589read_command([$ |Name0], [After], St) when St#state.filename == [] -> 590 Name = skip_blanks(Name0), 591 read(After, Name, St#state{filename=Name}); 592read_command([$ |Name0], [After], St) -> 593 Name = skip_blanks(Name0), 594 read(After, Name, St); 595read_command(_, _, _) -> 596 error(missing_space). 597 598read(After, Name, St0) -> 599 case file:read_file(Name) of 600 {ok, Bin} -> 601 Chars = size(Bin), 602 St1 = move_to(After, St0), 603 St2 = insert_line(binary_to_list(Bin), St1), 604 io:format("~w~n", [Chars]), 605 St2; 606 {error, _} -> 607 error(bad_file) 608 end. 609 610%% s/pattern/replacement/gp 611 612subst_command(_, [0, _], _) -> 613 error(bad_linenum); 614subst_command([$ |_Cmd0], [_First, _Last], _St0) -> 615 error(bad_delimiter); 616subst_command([$\n|_Cmd0], [_First, _Last], _St0) -> 617 error(bad_delimiter); 618subst_command([Sep|Cmd0], [First, Last], St0) -> 619 St1 = save_for_undo(St0), 620 {ok, Cmd1, St2} = get_pattern(Sep, Cmd0, St1), 621 {ok, Replacement, Cmd2} = get_replacement(Sep, Cmd1), 622 {ok, Opts, Cmd3} = subst_check_gflag(Cmd2), 623 St3 = check_trailing_p(Cmd3, St2), 624 subst_command(Last-First+1, Opts, Replacement, 625 move_to(First-1, St3), nomatch); 626subst_command([], _, _) -> 627 error(bad_delimiter). 628 629subst_command(0, _, _, _, nomatch) -> 630 error(nomatch); 631subst_command(0, _, _, _, StLast) when is_record(StLast, state) -> 632 StLast; 633subst_command(Left, Opts, Repl, St0, LastMatch) -> 634 St1 = next_line(St0), 635 [Line|_] = St1#state.upto_dot, 636 Contents = Line#line.contents, 637 case re:replace(Contents, St1#state.pattern, Repl, Opts) of 638 Contents -> 639 subst_command(Left-1, Opts, Repl, St1, LastMatch); 640 NewContents -> 641 %% XXX This doesn't work with marks. 642 St2 = delete_current_line(St1), 643 St3 = insert_line(NewContents, St2), 644 subst_command(Left-1, Opts, Repl, St3, St3) 645 end. 646 647subst_check_gflag([$g|Cmd]) -> {ok, [global,{return,list}], Cmd}; 648subst_check_gflag(Cmd) -> {ok, [{return,list}], Cmd}. 649 650%% u - undo 651 652undo_command([], [], St) when St#state.undo == undefined -> 653 error(bad_undo); 654undo_command([], [], #state{undo=Undo}) -> 655 Undo; 656undo_command(_, _, _) -> 657 error(garbage_after_command). 658 659%% (1,$)w - write buffer to file 660 661write_command(_Cmd, [_First, _Last], _St) -> 662 error(not_implemented). 663 664 665%%% Primitive buffer operations. 666 667print_current(St) -> 668 [Line|_] = St#state.upto_dot, 669 Printer = St#state.print, 670 Printer(Line#line.contents, St). 671 672delete_current_line(St) when St#state.dot == 0 -> 673 error(bad_linenum); 674delete_current_line(St) -> 675 Lines = St#state.lines, 676 [_|Prev] = St#state.upto_dot, 677 St#state{dot=St#state.dot-1, upto_dot=Prev, lines=Lines-1, modified=true}. 678 679insert_line(Line, State) -> 680 insert_line1(Line, State, []). 681 682insert_line1([$\n|Rest], State, Result) -> 683 NewState = insert_single_line(lists:reverse(Result, [$\n]), State), 684 insert_line1(Rest, NewState, []); 685insert_line1([C|Rest], State, Result) -> 686 insert_line1(Rest, State, [C|Result]); 687insert_line1([], State, []) -> 688 State; 689insert_line1([], State, Result) -> 690 insert_single_line(lists:reverse(Result, [$\n]), State). 691 692insert_single_line(Line0, State) -> 693 Line = #line{contents=Line0}, 694 Dot = State#state.dot, 695 Before = State#state.upto_dot, 696 Lines = State#state.lines, 697 %% XXX Avoid updating the record every time. 698 State#state{dot=Dot+1, upto_dot=[Line|Before], lines=Lines+1, modified=true}. 699 700move_to(Line, State) when Line < State#state.dot -> 701 move_to(Line, prev_line(State)); 702move_to(Line, State) when State#state.dot < Line -> 703 move_to(Line, next_line(State)); 704move_to(Line, State) when Line == State#state.dot -> 705 State. 706 707prev_line(State) -> 708 Dot = State#state.dot, 709 Before = State#state.upto_dot, 710 After = State#state.after_dot, 711 State#state{dot=Dot-1, upto_dot=tl(Before), after_dot=[hd(Before)|After]}. 712 713next_line(State) -> 714 Dot = State#state.dot, 715 Before = State#state.upto_dot, 716 After = State#state.after_dot, 717 State#state{dot=Dot+1, upto_dot=[hd(After)|Before], after_dot=tl(After)}. 718 719wrap_next_line(State) when State#state.dot == State#state.lines -> 720 move_to(1, State); 721wrap_next_line(State) -> 722 next_line(State). 723 724 725%%% Utilities. 726 727get_pattern(End, Cmd, State) -> 728 get_pattern(End, Cmd, State, []). 729 730get_pattern(End, [End|Rest], State, []) when State#state.pattern /= undefined -> 731 {ok, Rest, State}; 732get_pattern(End, [End|Rest], State, Result) -> 733 case re:compile(lists:reverse(Result)) of 734 {error, _} -> 735 error(bad_pattern); 736 {ok, Re} -> 737 {ok, Rest, State#state{pattern=Re}} 738 end; 739get_pattern(End, [C|Rest], State, Result) -> 740 get_pattern(End, Rest, State, [C|Result]); 741get_pattern(End, [], State, Result) -> 742 get_pattern(End, [End], State, Result). 743 744get_replacement(End, Cmd) -> 745 get_replacement(End, Cmd, []). 746 747get_replacement(End, [End|Rest], Result) -> 748 {ok, lists:reverse(Result), Rest}; 749get_replacement(End, [$\\, $&|Rest], Result) -> 750 get_replacement(End, Rest, [$&, $\\|Result]); 751get_replacement(End, [$\\, C|Rest], Result) -> 752 get_replacement(End, Rest, [C|Result]); 753get_replacement(End, [C|Rest], Result) -> 754 get_replacement(End, Rest, [C|Result]); 755get_replacement(End, [], Result) -> 756 get_replacement(End, [End], Result). 757 758check_trailing_p([$l], St) -> 759 St#state{print=fun(Line, _) -> lister(Line, 0) end}; 760check_trailing_p([$n], St) -> 761 St#state{print=fun numberer/2}; 762check_trailing_p([$p], St) -> 763 St#state{print=fun(Line, _) -> io:put_chars(Line) end}; 764check_trailing_p([], State) -> 765 State; 766check_trailing_p(_Other, _State) -> 767 error(garbage_after_command). 768 769error(Reason) -> 770 throw({error, Reason}). 771 772match(State) when State#state.dot == 0 -> 773 false; 774match(State) -> 775 [Line|_] = State#state.upto_dot, 776 Re = State#state.pattern, 777 case re:run(Line#line.contents, Re, [{capture, none}]) of 778 match -> true; 779 nomatch -> false 780 end. 781 782skip_blanks([$ |Rest]) -> 783 skip_blanks(Rest); 784skip_blanks(Rest) -> 785 Rest. 786 787print(Rest, [Line], St0) when Line > 0 -> 788 St1 = check_trailing_p(Rest, St0), 789 print(Line, move_to(Line-1, St1)); 790print(Rest, [First, Last], St0) when First > 0 -> 791 St1 = check_trailing_p(Rest, St0), 792 print(Last, move_to(First-1, St1)). 793 794print(Last, St) when St#state.dot == Last -> 795 St#state{print=false}; 796print(Last, St0) -> 797 St1 = next_line(St0), 798 print_current(St1), 799 print(Last, St1). 800 801lister(Rest, 64) -> 802 io:put_chars("\\\n"), 803 lister(Rest, 0); 804lister([C|Rest], Num) -> 805 list_char(C), 806 lister(Rest, Num+1); 807lister([], _) -> 808 ok. 809 810list_char($\t) -> 811 io:put_chars("\\t"); 812list_char($\n) -> 813 io:put_chars("$\n"); 814list_char(C) -> 815 io:put_chars([C]). 816 817numberer(Line, St) -> 818 io:format("~w\t~s", [St#state.dot, Line]). 819 820save_for_undo(St) -> 821 St#state{undo=St#state{undo=undefined, print=false}}. 822 823save_for_undo(St, OldSt) -> 824 St#state{undo=OldSt#state{undo=undefined, print=false}}. 825