1/* 2 3 Minimum Profit - A Text Editor 4 File manipulation. 5 6 ttcdt <dev@triptico.com> et al. 7 8 This software is released into the public domain. 9 NO WARRANTY. See file LICENSE for details. 10 11*/ 12 13/** RTF config tweaks **/ 14 15mp.config.rtf_font_face = 'Times New Roman'; 16mp.config.rtf_font_size = 12; 17mp.config.rtf_mono_font_face = 'Courier New'; 18mp.config.rtf_mono_font_size = 10; 19mp.config.rtf_style_default = '\sa227\sl480\slmult1'; 20mp.config.rtf_style_first = '\qj\fi0'; 21mp.config.rtf_style_para = '\qj\fi567'; 22mp.config.rtf_style_center = '\qc\fi0'; 23mp.config.rtf_style_verbatim = '\ql\fi0\sl0'; 24mp.config.troff_font_size = 12; 25mp.config.troff_page_height = 10; 26mp.config.html_title = ''; 27 28/** editor actions **/ 29 30mp.actions['new'] = sub { 31 local d = mp.find_file_by_name(L("<unnamed>")); 32 33 if (d != -1) 34 d = mp.set_active(d); 35 else 36 d = mp.add(new(mp_doc, { is_new: 1 })); 37 38 return d; 39}; 40 41mp.actions['next'] = sub { mp.next(); }; 42mp.actions['prev'] = sub { mp.prev(); }; 43 44mp_doc.actions['save_as'] = sub (d, newname) { 45 46 if (newname == NULL) 47 newname = mp.savefile(L("Save file as") + ':'); 48 49 if (newname != NULL) { 50 /* store new name */ 51 d.name = newname; 52 53 if (d->long_op(d.save) == -1) 54 mp.alert(sprintf(L("Error saving '%s': %s"), 55 mp.trim(d.name, 32), ERRNO)); 56 else 57 d->detect_syntax(); 58 } 59 60 return d; 61}; 62 63mp_doc.actions['save'] = sub (d) { 64 65 /* name is <unnamed> or something similar; ask for one */ 66 if (regex(d.name, "/^<.+>$/")) 67 d->actions.save_as(); 68 else 69 if (d->long_op(d.save) == -1) 70 mp.alert(sprintf(L("Error saving '%s': %s"), 71 mp.trim(d.name, 32), ERRNO)); 72 73 return d; 74}; 75 76mp_doc.actions['export'] = sub (d, filename) { 77 local ret = 0; 78 79 mp.load_on_demand(mp_mptxt, "mp_mptxt.mpsl"); 80 81 local formats = mp_mptxt.exporters->map(index)->join(", "); 82 83 if (filename == NULL) 84 filename = mp.savefile(L("Export to file (available formats: ") 85 + formats + '):'); 86 87 if (filename != NULL) { 88 local format = filename->split(".")->pop()->lc(); 89 90 d->busy(1); 91 92 ret = mp_mptxt.export(d, format); 93 94 d->busy(0); 95 96 if (ret == NULL) 97 mp.alert(sprintf(L("Unsupported format '%s'.\n(Not one of: [%s])"), 98 format, formats)); 99 else { 100 local p_enc = ENCODING; 101 encoding(mp_mptxt.encodings[format]); 102 103 if ((f = open(filename, "wb"))) { 104 f->write(ret)->close(); 105 } 106 else { 107 mp.alert(sprintf(L("Error exporting '%s': %s"), 108 mp.trim(filename, 32), ERRNO)); 109 } 110 111 encoding(p_enc); 112 } 113 } 114}; 115 116mp.actions['close'] = sub (d) { 117 if (d->query_close() != 0) 118 mp.close(); 119}; 120 121mp.actions['exit'] = sub { 122 if (mp.config.auto_sessions) 123 mp.save_session(); 124 125 if (mp.actions.close_all()) 126 mp_c.exit(); 127}; 128 129mp.actions['suspend'] = sub { 130 mp_drv.suspend(); 131}; 132 133mp.actions['open'] = sub (d, filename) { 134 if (filename == NULL) 135 filename = mp.openfile(L("File to open") + ':'); 136 137 if (filename != NULL && filename != "") { 138 if (mp.long_op(mp.open, filename) == NULL) { 139 if (ERRNO == NULL) { 140 /* not open but no ERRNO: it's encrypted */ 141 local f = mp.form( 142 [ 143 { 144 type: 'password', 145 label: L("Password") + ':' 146 } 147 ] 148 ); 149 150 if (f != NULL) { 151 if (mp.long_op(mp.open, filename, f[0]) == NULL) 152 mp.alert(sprintf(L("Bad password for file '%s'"), 153 mp.trim(filename, 32))); 154 } 155 } 156 else 157 mp.alert(sprintf(L("Error opening '%s': %s"), 158 mp.trim(filename, 32), ERRNO)); 159 } 160 } 161 162 return filename; 163}; 164 165 166mp.actions['open_recent'] = sub (d) { 167 /* create the list of filenames... */ 168 local filenames = mp.state.files->map(index)-> 169 /* delete all those not having a timestamp... */ 170 grep(sub (v, i) { exists(mp.state.files[v], 't'); })-> 171 172 /* sort by timestamp... */ 173 sort(sub (a, b) { cmp(mp.state.files[b].t, mp.state.files[a].t); })-> 174 175 /* and add a timestamp for humans */ 176 map(sub (v) { v + sprintf("\t%t{%Y-%m-%d %H:%M:%S}", mp.state.files[v].t); }); 177 178 local form = mp.form( 179 [ 180 { 181 label: L("Recent files") + ':', 182 type: 'list', 183 list: filenames 184 } 185 ] 186 ); 187 188 if (form != NULL) { 189 mp.actions.open(d, filenames[form[0]]->split("\t")->shift()); 190 } 191}; 192 193 194mp.actions['open_folder'] = sub { 195 local f; 196 197 if ((f = mp_drv.openfolder(L("Folder to open"))) != NULL) 198 mp.actions.open(NULL, f); 199}; 200 201mp_doc.actions['revert'] = sub (d) { 202 /* save current name */ 203 local p = d.name; 204 205 if (d.txt.mod) { 206 local r; 207 r = mp.confirm(sprintf(L("'%s' has changed. Are you sure?"), 208 mp.trim(p, 32))); 209 210 /* cancel? don't close */ 211 if (r == 0 || r == 2) 212 return d; 213 } 214 215 d->store_undo(); 216 217 mp.close(); 218 if (mp.long_op(mp.open, p) == NULL && ERRNO != NULL) 219 mp.alert(sprintf("Error opening '%s': %s", p, ERRNO)); 220 221 local nd = mp.active(); 222 223 nd.undo_q = d.undo_q; 224 225 nd->set_y(d.txt.y)->set_x(d.txt.x); 226 nd.txt.vy = d.txt.vy; 227 228 return nd; 229}; 230 231mp.actions['open_config_file'] = sub { 232 mp.open(mp.config_file); 233}; 234 235mp.actions['sync'] = sub { 236 /* save all modified documents */ 237 foreach (d, grep(mp.docs, sub (e) { e.txt.mod; })) 238 d->actions.save(); 239 240 return d; 241}; 242 243mp_doc.actions['exec_command'] = sub (d, cmd) { 244 if (cmd == NULL) { 245 local t = mp.form( 246 [ 247 { 248 label: L("System command") + ':', 249 type: 'text', 250 history: 'system' 251 } 252 ] 253 ); 254 255 if (t != NULL) 256 cmd = t[0]; 257 } 258 259 if (cmd != NULL) { 260 /* does it start with a pipe? */ 261 if (regex(cmd, '/^\|/')) { 262 local p; 263 264 /* yes; current document should be fed to it */ 265 cmd = sregex(cmd, '/^\|/'); 266 267 if ((p = popen(cmd, "w")) != NULL) { 268 mp.busy(1); 269 270 foreach (l, d->get_active_area()->clone()->mp_c.vw_unwrap()) 271 write(p, l + mp.config.eol); 272 273 pclose(p); 274 mp.busy(0); 275 } 276 else 277 mp.alert(sprintf(L("Error writing to command '%s'"), cmd)); 278 } 279 else { 280 /* no; execute command and insert into cursor */ 281 local p; 282 283 if ((p = popen(cmd, "r")) != NULL) { 284 local l; 285 286 d->store_undo(); 287 mp.busy(1); 288 289 /* prevent auto indenting the command output */ 290 local p_config = clone(mp.config); 291 292 mp.config.auto_indent = 0; 293 294 foreach (l, p) 295 d->insert(l); 296 297 mp.config = p_config; 298 299 /* invalidate window size to trigger a visual re-wrap */ 300 mp.txt.last_tx = 0; 301 302 pclose(p); 303 mp.busy(0); 304 } 305 else 306 mp.alert(sprintf(L("Error reading from command '%s'"), cmd)); 307 } 308 } 309 310 return d; 311}; 312 313mp_doc.actions['filter_selection'] = sub (d, cmd) { 314 if (cmd == NULL) { 315 local t = mp.form( 316 [ 317 { 318 'label' => L("System command") + ':', 319 'type' => 'text', 320 'history' => 'system2' 321 } 322 ] 323 ); 324 325 if (t != NULL) 326 cmd = t[0]; 327 } 328 329 if (cmd != NULL) { 330 d->store_undo(); 331 332 /* if there is no selection, take full document */ 333 if (d.txt.mark == NULL) { 334 d->move_bof()->mark()->move_eof()->mark(); 335 } 336 337 /* take it out */ 338 d->cut(); 339 340 /* now feed it to the command */ 341 local p = popen2(cmd); 342 343 if (p != NULL) { 344 write(p[1], join(mp.clipboard, "\n")); 345 pclose(p[1]); 346 347 local l; 348 while ((l = read(p[0])) != NULL) 349 d->insert(l); 350 351 pclose(p[0]); 352 } 353 } 354 355 return d; 356}; 357 358mp.actions['close_all'] = sub { 359 local s; 360 361 while (s = size(mp.docs)) { 362 local doc = mp.docs[mp.active_i]; 363 364 /* close current document */ 365 mp.actions.close(doc); 366 367 /* if the size of the list hasn't changed, 368 action was cancelled, so don't exit */ 369 if (s == size(mp.docs)) 370 return 0; 371 } 372 373 return 1; 374}; 375 376mp_doc.actions['open_under_cursor'] = sub (d) { 377 local w; 378 379 if (d.txt.lines[d.txt.y]->regex('/^@@ /')) { 380 /* it's a diff mark */ 381 382 /* pick line */ 383 local l = d.txt.lines[d.txt.y]->regex('/[0-9]+/'); 384 local f; 385 local y = d.txt.y - 1; 386 387 foreach (_, y) { 388 /* pick filename (stripping mark from previous line) */ 389 if (d.txt.lines[y]->regex('/^\+\+\+ /')) { 390 f = d.txt.lines[y]->sregex('/^\+\+\+ .\//'); 391 break; 392 } 393 } 394 395 if (f != NULL) 396 d = mp.open(f + ':' + l + ':'); 397 } 398 else 399 if ((w = d->get_word(regcomp("/[^ \t\"\'<>]+/"))) != NULL) 400 d = mp.open(w); 401 402 return d; 403}; 404 405 406sub mp.load_on_demand(sym, src) 407/* loads a source code file on demand and whines if it can't */ 408{ 409 local ok = bool(1); 410 411 if (sym == NULL) { 412 eval(sub { load(src); } ); 413 414 if (ERROR) { 415 mp.alert(ERROR); 416 ERROR = NULL; 417 ok = !ok; 418 } 419 } 420 421 return ok; 422} 423 424 425mp.actions['hex_view'] = sub (doc, filename) { 426 local d = NULL; 427 428 if (filename == NULL) 429 filename = mp.openfile(L("File to open") + ':'); 430 431 if (filename != NULL && filename != "") { 432 if (mp.load_on_demand(mp_hex_view, "mp_hex.mpsl")) { 433 d = new(mp_hex_view, { name: filename })->init(); 434 435 if (d == NULL) 436 mp.alert(sprintf("Error opening '%s': %s", 437 mp.trim(filename, 32), ERRNO)); 438 else 439 mp.add(d); 440 } 441 } 442 443 return d; 444}; 445 446mp.actions['open_dropped_files'] = sub { 447 while (size(mp.dropped_files)) 448 mp.open(shift(mp.dropped_files)); 449}; 450 451 452mp_doc.actions["idle"] = sub (d) { 453 /* do nothing */ 454 return d; 455}; 456 457 458/** default key bindings **/ 459 460mp.keycodes['ctrl-n'] = 'next'; 461mp.keycodes['ctrl-o'] = 'open'; 462mp.keycodes['ctrl-q'] = 'exit'; 463mp.keycodes['ctrl-l'] = 'suspend'; 464mp.keycodes['ctrl-w'] = 'close'; 465mp.keycodes['dropped-files'] = 'open_dropped_files'; 466mp.keycodes['close-window'] = 'exit'; 467mp_doc.keycodes['ctrl-s'] = 'save'; 468mp_doc.keycodes['ctrl-enter'] = 'open_under_cursor'; 469mp_doc.keycodes['alt-enter'] = 'ctrl-enter'; 470mp_doc.keycodes['alt-o'] = 'ctrl-enter'; 471mp_doc.keycodes['ctrl-k'] = "toggle_visual_wrap"; 472mp_doc.keycodes["idle"] = "idle"; 473 474/** action descriptions **/ 475 476mp.actdesc['new'] = LL("New"); 477mp.actdesc['save'] = LL("Save..."); 478mp.actdesc['save_as'] = LL("Save as..."); 479mp.actdesc['export'] = LL("Export..."); 480mp.actdesc['next'] = LL("Next"); 481mp.actdesc['prev'] = LL("Previous"); 482mp.actdesc['open'] = LL("Open..."); 483mp.actdesc['exit'] = LL("Exit"); 484mp.actdesc['suspend'] = LL("Suspend"); 485mp.actdesc['close'] = LL("Close"); 486mp.actdesc['revert'] = LL("Revert"); 487mp.actdesc['close_all'] = LL("Close all"); 488mp.actdesc['open_folder'] = LL("Open folder..."); 489 490mp.actdesc['open_config_file'] = LL("Edit configuration file"); 491mp.actdesc['sync'] = LL("Save modified texts"); 492mp.actdesc['exec_command'] = LL("Run system command..."); 493mp.actdesc['filter_selection'] = LL("Filter selection through system command..."); 494mp.actdesc['open_under_cursor'] = LL("Open file under cursor"); 495mp.actdesc['hex_view'] = LL("Hexadecimal viewer..."); 496mp.actdesc['open_dropped_files'] = LL("Open dropped files"); 497mp.actdesc['open_recent'] = LL("Open recent files..."); 498mp.actdesc['toggle_visual_wrap'] = LL("Toggle visual wrap"); 499mp.actdesc['idle'] = LL("Called when idle"); 500 501/** code **/ 502 503sub mp.chomp(str) 504/* chomps the end of file chars from a string */ 505{ 506 sregex(str, "/\r*\n*$/"); 507} 508 509 510sub mp_doc.recov_file_name(doc) 511/* returns the recovery file name for this document */ 512{ 513 local fn = doc.name; 514 515 /* if it's not a recovery file itself, calculate it */ 516 if (!regex(fn, "/\.recov$")) 517 fn = HOMEDIR + ".mp-" + md5(fn) + ".recov"; 518 519 return fn; 520} 521 522 523sub mp_doc.pre_event(doc, k) 524/* an event is to be processed */ 525{ 526 if (doc.mtime && time() > mp.mtime_test) { 527 local s; 528 529 if ((s = stat(doc.name)) != NULL && s[9] > doc.mtime) { 530 if (mp.confirm(sprintf(L("'%s' was changed externally. Reload?"), 531 mp.trim(doc.name, 32))) == 1) { 532 doc->actions.revert(); 533 k = NULL; 534 } 535 536 doc.mtime = s[9]; 537 } 538 539 mp.mtime_test = time() + 2; 540 } 541 542 return k; 543} 544 545 546sub mp_doc.post_event(doc, k) 547/* an event has just been processed */ 548{ 549 /* if it's read only but has modifications, revert them */ 550 if (doc.read_only && size(doc.undo_q)) { 551 while (size(doc.undo_q)) 552 doc->undo(); 553 554 /* forget the last insert time */ 555 doc.last_insert = 0; 556 557 mp.message = { 558 timeout: time() + 2, 559 string: '*' + L("Read-only document") + '*' 560 }; 561 } 562 563 /* only try auto save if it's not disabled */ 564 if (mp.config.auto_save_period) 565 mp.auto_save(); 566 567 return doc; 568} 569 570 571sub mp.auto_save() 572/* auto save recovery files */ 573{ 574 local t = time(); 575 576 foreach (doc, mp.docs) { 577 if (doc.txt.mod) { 578 /* first time? just store current time */ 579 if (doc.auto_save_time == NULL) 580 doc.auto_save_time = t; 581 else 582 /* time to check again? */ 583 if (t > doc.auto_save_time + mp.config.auto_save_period) { 584 local f; 585 586 if ((f = open(doc->recov_file_name(), "w")) != NULL) { 587 f->write( 588 sprintf(L("RECOVERED DATA for %s at %t{%Y-%m-%d %H:%M:%S}"), 589 doc.name, t)); 590 591 foreach (l, doc.txt.lines) 592 f->write("\n")->write(l); 593 594 f->close(); 595 } 596 597 /* store checked time */ 598 doc.auto_save_time = t; 599 } 600 } 601 else { 602 /* reset timer */ 603 doc.auto_save_time = NULL; 604 } 605 } 606} 607 608 609sub mp_doc.query_close(doc) 610/* queries a close; if it returns 0, the closing is rejected */ 611{ 612 local r = 1; 613 614 if (doc.txt.mod) { 615 r = mp.confirm(sprintf(L("'%s' has changed. Save changes?"), 616 mp.trim(doc.name, 32))); 617 618 /* confirm? save */ 619 if (r == 1) 620 doc->actions.save(); 621 } 622 623 return r; 624} 625 626 627sub mp.save_th(f, doc) 628/* mp.save() helper */ 629{ 630 local eol = mp.config.keep_eol && doc.eol || mp.config.eol; 631 local lines; 632 633 doc.disk_op = 1; 634 635 if (doc->visual_wrap()) 636 lines = clone(doc.txt.lines)->mp_c.vw_unwrap(); 637 else 638 lines = doc.txt.lines; 639 640 /* save as a plain text file */ 641 foreach (l, nl, lines) { 642 /* write a line separator if it's not the first line */ 643 if (nl) 644 write(f, eol); 645 646 write(f, l); 647 } 648 649 doc.disk_op = 0; 650 651 return nl; 652} 653 654 655sub mp_doc.save(doc) 656/* saves a file */ 657{ 658 local f; 659 local s = NULL; 660 local ret = 0; 661 662 /* if unlink before write is desired, do it */ 663 if (mp.config.unlink && (s = stat(doc.name)) != NULL) 664 unlink(doc.name); 665 666 /* set the encoding for this file opening */ 667 TEMP_ENCODING = doc.encoding; 668 669 if ((f = open(doc.name, "wb")) == NULL) { 670 /* can't write? delete name */ 671 doc.name = L("<unnamed>"); 672 ret = -1; 673 } 674 else { 675 ret = 0; 676 677 /* if the document has a password, save it encrypted */ 678 if (doc.password) 679 mp.crypt_save(f, doc.txt.lines, doc.password, doc.crypt_ver); 680 else 681 mp.save_th(f, doc); 682 683 close(f); 684 doc.txt.mod = 0; 685 686 /* set back the permissions and ownership, if available */ 687 if (s != NULL) { 688 chmod(doc.name, s[2]); 689 chown(doc.name, s[4], s[5]); 690 } 691 692 s = stat(doc.name); 693 doc.mtime = s[9]; 694 doc.is_new = 0; 695 696 /* delete the recovery file */ 697 unlink(doc->recov_file_name()); 698 } 699 700 return ret; 701} 702 703 704sub mp_doc.long_op(doc, func, a2, a3, a4) 705{ 706 local r; 707 708 doc->busy(1); 709 r = doc->func(a2, a3, a4); 710 doc->busy(0); 711 712 return r; 713} 714 715 716sub mp.add(doc) 717/* adds a doc to the list of documents */ 718{ 719 /* store in the list and set as active */ 720 ins(mp.docs, doc, mp.active_i); 721 722 return mp.set_active(mp.active_i); 723} 724 725 726sub mp.next() 727/* rotates through the document list */ 728{ 729 return mp.set_active((mp.active_i + 1) % count(mp.docs)); 730} 731 732 733sub mp.prev() 734/* rotates through the document list, backwards */ 735{ 736 return mp.set_active((mp.active_i - 1 + count(mp.docs)) % count(mp.docs)); 737} 738 739 740sub mp_doc.state(doc) 741/* returns a structure to be store into the state */ 742{ 743 local state = NULL; 744 745 if (!regex(doc.name, "/^</") && !doc.no_state && !doc.is_new) 746 state = { vy: doc.txt.vy, o: doc->get_offset(), t: time() }; 747 748 return state; 749} 750 751 752sub mp.close() 753/* closes the active document */ 754{ 755 /* no state for files? create it */ 756 if (!exists(mp.state, 'files')) 757 mp.state.files = {}; 758 759 /* store in the state of files */ 760 local l = mp.active(); 761 762 local state = l->state(); 763 764 if (state != NULL) 765 mp.state.files[l.name] = state; 766 767 /* delete the recovery file */ 768 if (l->exists("recov_file_name")) 769 unlink(l->recov_file_name()); 770 771 /* delete from the list */ 772 del(mp.docs, mp.active_i); 773 774 /* rotate if it was the last one */ 775 if (mp.active_i == size(mp.docs)) 776 mp.active_i = 0; 777 778 mp.set_active(mp.active_i); 779 780 /* cannot call mp.active() */ 781} 782 783 784sub mp.find_file_by_name(filename) 785/* finds an open file by its name */ 786{ 787 mp.docs->map(sub(d) { d.name; })->seek(filename); 788} 789 790 791sub mp.open(filename, password) 792/* opens a new document */ 793{ 794 local d, s, f; 795 local x, y; 796 797 /* test if filename includes :y: or :y:x: positioning */ 798 if (regex(filename, "/.+:[0-9]+:([0-9]+:)?$/")) { 799 local l = split(filename, ':'); 800 801 x = integer(l[2]); 802 y = integer(l[1]); 803 filename = l[0]; 804 } 805 806 if ((s = mp.find_file_by_name(filename)) != -1) { 807 /* already open */ 808 d = mp.set_active(s); 809 } 810 else 811 if ((s = stat(filename)) == NULL) { 812 /* non-existent file; create as new */ 813 mp.message = { 814 timeout: time() + 2, 815 string: sprintf(L("New file '%s'"), filename) 816 }; 817 818 ERRNO = NULL; 819 820 d = mp.add(new(mp_doc, { name: filename, is_new: 1 })); 821 } 822 else 823 if (s[13] != NULL) { 824 local r; 825 826 /* canonicalize, if possible */ 827 filename = s[13]; 828 829 /* look again for this filename in the open files */ 830 if ((r = mp.find_file_by_name(filename)) != -1) 831 d = mp.set_active(r); 832 } 833 834 if (d == NULL) { 835 /* still not open: load */ 836 f = open(filename, "rb"); 837 838 if (f != NULL) { 839 local ver; 840 841 if ((ver = mp.crypt_detect(f))) { 842 ERRNO = NULL; 843 844 if (password != NULL && password != '') { 845 /* and load the file */ 846 local lines = mp.crypt_load(f, password, ver); 847 848 if (lines != NULL) { 849 d = mp.add(new(mp_doc, { name: filename })); 850 d.txt.lines = lines; 851 d.password = password; 852 d.crypt_ver = ver; 853 } 854 } 855 } 856 else { 857 /* mp.crypt_detect() called read(), so there 858 is (possibly) and eol already detected */ 859 local e = eol(f); 860 861 /* close file (needed for rewinding AND 862 possible encoding autodetection) */ 863 close(f); 864 865 /* reopen and read in auto-chomp mode */ 866 AUTO_CHOMP = 1; 867 868 f = open(filename, "rb"); 869 d = mp.add(new(mp_doc, { name: filename })); 870 871 d.txt.lines = map(f); 872 873 if (count(d.txt.lines) == 0) 874 d.txt.lines->push(''); 875 876 /* store the detected eol or the default one */ 877 d.eol = e || mp.config.eol; 878 879 AUTO_CHOMP = 0; 880 } 881 882 close(f); 883 } 884 else 885 if (bitand(s[2], 0040000)) { 886 /* it's a directory: show it */ 887 ERRNO = NULL; 888 889 if (mp.load_on_demand(mp_doc_dir, "mp_dir.mpsl")) { 890 d = mp.add(new(mp_doc_dir, { name: filename })); 891 d->setup(); 892 } 893 } 894 895 if (d) { 896 /* store mtime */ 897 d.mtime = s[9]; 898 899 /* store the encoding */ 900 d.encoding = DETECTED_ENCODING || ENCODING || ''; 901 902 d->detect_syntax(); 903 904 /* ignore further system errors */ 905 ERRNO = NULL; 906 } 907 } 908 909 if (d) { 910 /* look in the state if we have a previous position */ 911 local p; 912 if ((p = mp.state.files[filename]) != NULL) { 913 /* if it has an offset, use it */ 914 if (p->exists("o")) 915 d->set_offset(integer(p.o)); 916 else { 917 d->set_y(integer(p.y)); 918 d->set_x(integer(p.x)); 919 } 920 921 d.txt.vy = integer(p.vy); 922 } 923 924 if (y) 925 d->search_set_y(y - 1); 926 if (x) 927 d->set_x(x - 1); 928 929 /* filter the document through the spellchecker (if it's active) */ 930 mp.spell_document(d); 931 } 932 933 return d; 934} 935 936 937/** visual wrap **/ 938 939sub mp.vw_col() 940/* returns the column for visual wrap */ 941{ 942 local tx = mp.window.tx; 943 944 if (tx > mp.config.double_page) 945 tx /= 2; 946 947 return tx - 2; 948} 949 950 951sub mp_doc.vw_unwrap(doc) 952/* unwraps a document, keeping the position */ 953{ 954 doc->busy(1); 955 local o = doc->get_offset(); 956 mp_c.vw_unwrap(doc.txt.lines); 957 doc->set_offset(o); 958 doc->busy(0); 959} 960 961 962sub mp_doc.vw_wrap(doc) 963/* wraps a document, keeping the position */ 964{ 965 doc->busy(1); 966 local o = doc->get_offset(); 967 mp_c.vw_wrap(doc.txt.lines, mp.vw_col()); 968 doc->set_offset(o); 969 doc.txt.vx = 0; 970 doc->busy(0); 971} 972 973 974mp_doc.actions["toggle_visual_wrap"] = sub (d) { 975 mp.config.visual_wrap = !mp.config.visual_wrap; 976 977 if (d->visual_wrap()) 978 d->vw_wrap(); 979 else 980 d->vw_unwrap(); 981}; 982 983 984sub mp_doc.get_offset(doc) 985/* returns the offset of the cursor from the document */ 986{ 987 return mp_c.get_offset(doc.txt.lines, doc.txt.y) + doc.txt.x; 988} 989 990 991sub mp_doc.set_offset(doc, o) 992/* moves the cursor to an absolute offset */ 993{ 994 local r = mp_c.set_offset(doc.txt.lines, o); 995 996 doc->set_y(r[1]); 997 doc->set_x(r[0]); 998 txt.vx = 0; 999} 1000