summaryrefslogtreecommitdiff
path: root/appl/wm/edit.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/wm/edit.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/wm/edit.b')
-rw-r--r--appl/wm/edit.b730
1 files changed, 730 insertions, 0 deletions
diff --git a/appl/wm/edit.b b/appl/wm/edit.b
new file mode 100644
index 00000000..dd6e8229
--- /dev/null
+++ b/appl/wm/edit.b
@@ -0,0 +1,730 @@
+#
+# Copyright © 1996-1999 Lucent Technologies Inc. All rights reserved.
+# Modified version of edit
+# D.B.Knudsen
+# Revisions Copyright © 2000-2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+implement WmEdit;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Rect, Screen: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+WmEdit: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+ErrIco: con "error -fg red";
+
+ed: ref Tk->Toplevel;
+dirty := 0;
+
+BLUE : con "#0000ff";
+GREEN : con "#008800";
+
+SEARCH,
+SEARCHFOR,
+REPLACE,
+REPLACEWITH,
+REPLACEALL,
+NOSEE : con iota;
+
+ed_config := array[] of {
+ "frame .m -relief raised -bd 2",
+ "frame .b",
+ "menubutton .m.file -text File -menu .m.file.menu",
+ "menubutton .m.edit -text Edit -menu .m.edit.menu",
+ "menubutton .m.search -text Search -menu .m.search.menu",
+ "menubutton .m.options -text Options -menu .m.options.menu",
+# "label .m.filename",
+ "pack .m.file .m.edit .m.search .m.options -side left",
+# "pack .m.filename -padx 10 -side left",
+ "menu .m.file.menu",
+ ".m.file.menu add command -label New -command {send c new}",
+ ".m.file.menu add command -label Open... -command {send c open}",
+ ".m.file.menu add separator",
+ ".m.file.menu add command -label Save -command {send c save}",
+ ".m.file.menu add command -label {Save As...} -command {send c saveas}",
+ ".m.file.menu add separator",
+ ".m.file.menu add command -label {Exit} -command {send c exit}",
+ "menu .m.edit.menu",
+ ".m.edit.menu add command -label Cut -command {send c cut}",
+ ".m.edit.menu add command -label Copy -command {send c copy}",
+ ".m.edit.menu add command -label Paste -command {send c paste}",
+ "menu .m.search.menu",
+ ".m.search.menu add command -label {Find ...} " +
+ "-command {send c searchf}",
+ ".m.search.menu add command -label {Replace with...} " +
+ "-command {send c replacew}",
+ ".m.search.menu add command -label {Find Again} -command {send c search}",
+ ".m.search.menu add command -label {Find and Replace} " +
+ "-command {send c replace}",
+ ".m.search.menu add command -label {Find and Replace All} " +
+ "-command {send c replaceall}",
+ "menu .m.options.menu",
+ ".m.options.menu add checkbutton -text Limbo -command {send c limbo}",
+ ".m.options.menu add command -label Indent -command {send c indent}",
+ "text .b.t -yscrollcommand {.b.s set} -bg white",
+ "bind .b.t <Button-2> {.m.edit.menu post %X %Y}",
+ "bind .b.t <Key> +{send c dirtied {%A}}",
+ "bind .b.t <ButtonRelease-1> +{send c reindent}",
+ "scrollbar .b.s -command {.b.t yview}",
+ "pack .m -fill x",
+ "pack .b.s -fill y -side left",
+ "pack .b.t -fill both -expand 1",
+ "pack .b -fill both -expand 1",
+ "focus .b.t",
+ "pack propagate . 0",
+ ".b.t tag configure keyword -fg " + BLUE,
+ ".b.t tag configure comment -fg " + GREEN,
+ "update",
+};
+
+context : ref Draw->Context;
+curfile := "(New)";
+snarf := "";
+searchfor := "";
+replacewith := "";
+path := ".";
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ wmctl: chan of string;
+
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+ dialog = load Dialog Dialog->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ selectfile->init();
+ dialog->init();
+
+ context = ctxt;
+
+ (ed, wmctl) = tkclient->toplevel(context, "", "Edit", Tkclient->Appl);
+
+ argv = tl argv;
+
+ c := chan of string;
+ tk->namechan(ed, c, "c");
+ for (i := 0; i < len ed_config; i++)
+ cmd(ed, ed_config[i]);
+
+ if (argv != nil) {
+ e := loadtfile(hd argv);
+ if(e != nil)
+ dialog->prompt(ctxt, ed.image, ErrIco, "Open file", e, 0, "Ok"::nil);
+ }
+
+ tkclient->settitle(ed, "Edit " + curfile);
+ tkclient->onscreen(ed, nil);
+ tkclient->startinput(ed, "ptr" :: "kbd" :: nil);
+ cmd(ed, "update");
+
+ e := cmd(ed, "variable lasterror");
+ if(e != "") {
+ sys->print("edit error: %s\n", e);
+ return;
+ }
+
+ cmdloop: for(;;) {
+ alt {
+ key := <-ed.ctxt.kbd =>
+ tk->keyboard(ed, key);
+ m := <-ed.ctxt.ptr =>
+ tk->pointer(ed, *m);
+ s := <-ed.ctxt.ctl or
+ s = <-ed.wreq or
+ s = <-wmctl =>
+ if(s == "exit") {
+ if (check_dirty())
+ break cmdloop;
+ else
+ break;
+ }
+ task_title: string;
+ if (s == "task") {
+ if (curfile == "(New)")
+ task_title = tkclient->settitle(ed, "Edit");
+ else
+ task_title = tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "update");
+ }
+ tkclient->wmctl(ed, s);
+ if (s == "task")
+ tkclient->settitle(ed, task_title);
+ s := <-c =>
+ if ( len s > 7 && s[:7] == "dirtied" ) {
+ set_dirty(); do_limbo_check(s);
+ }
+ else
+ case s {
+ "exit" => if ( check_dirty() ){ set_clean(); break cmdloop; }
+ "dirtied" => set_dirty(); do_limbo_check(s);
+ "new" => if ( check_dirty()) {set_clean(); do_new();}
+ "open" => if ( check_dirty() && do_open()) set_clean();
+ "save" => do_save(0);
+ "saveas" => do_save(1);
+ "cut" => do_snarf(1); set_dirty();
+ "copy" => do_snarf(0);
+ "paste" => do_paste(); set_dirty();
+ "search" => do_search(SEARCH);
+ "searchf" => do_search(SEARCHFOR);
+ "replace" => do_replace(REPLACE);
+ "replacew" => do_replace(REPLACEWITH);
+ "replaceall" => do_replaceall();
+ "limbo" => do_limbo();
+ "indent" => do_indent();
+ "reindent" => re_indent();
+ }
+ cmd(ed, "focus .b.t");
+ }
+ cmd(ed, "update");
+ e = cmd(ed, "variable lasterror");
+ if(e != "") {
+ sys->print("edit error: %s\n", e);
+ break cmdloop;
+ }
+ }
+}
+
+check_dirty() : int
+{
+ if ( dirty == 0 )
+ return 1;
+ if (dialog->prompt(context, ed.image, ErrIco, "Confirm",
+ "File was changed.\nDiscard changes?",
+ 0, "Yes" :: "No" :: nil) == 0 ) {
+ return 1;
+ }
+ return 0;
+}
+
+set_dirty()
+{
+ if(!dirty){
+ dirty = 1;
+ tkclient->settitle(ed, "Edit " + curfile + " (dirty)");
+ cmd(ed, "update");
+ }
+# We want to just remove the binding, but Inferno's tk does not
+# recognize the - in front of the command. To make it do so would
+# require changes to utils.c and ebind.c in /tk
+# cmd(ed, "bind .b.t <Key> -{send c dirtied}");
+}
+
+set_clean()
+{
+ if(dirty){
+ dirty = 0;
+ tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "update");
+ #cmd(ed, "bind .b.t <Key> +{send c dirtied}");
+ }
+}
+
+BLOCK, TEMP : con iota;
+is_limbo := 0; # initially not limbo
+this_word := "";
+last_keyword := "";
+in_comment := 0;
+first_char := 1;
+indent : list of int;
+last_kw_is_block := 0;
+tab := "\t";
+tabs := array[] of {
+ "", "\t", "\t\t", "\t\t\t", "\t\t\t\t", "\t\t\t\t\t",
+ "\t\t\t\t\t\t", "\t\t\t\t\t\t\t", "\t\t\t\t\t\t\t\t"
+};
+
+keywords := array[] of {
+ "adt", "alt", "array", "big", "break",
+ "byte", "case", "chan", "con", "continue",
+ "cyclic", "do", "else", "exit", "fn",
+ "for", "hd", "if", "implement", "import",
+ "include", "int", "len", "list", "load",
+ "module", "nil", "of", "or", "pick",
+ "real", "ref", "return", "self", "spawn",
+ "string", "tagof", "tl", "to", "type",
+ "while"
+};
+block_keyword := (big 1 << 40 ) | big (1 << 17) | big (1 << 15) |
+ big (1 << 12) | big (1 << 11);
+
+do_limbo()
+{
+ is_limbo = !is_limbo;
+ if ( is_limbo )
+ mark_keyw_comm();
+ else {
+ cmd(ed, ".b.t tag remove comment 1.0 end");
+ cmd(ed, ".b.t tag remove keyword 1.0 end");
+ }
+}
+
+do_limbo_check(s : string)
+{
+ if ( ! is_limbo )
+ return;
+ if ( len s < 11 )
+ return;
+#
+# Maybe we should actually remember where the insert point is.
+# In general we can get it via .b.t index insert, but for most
+# characters, we could maintain the position with simple arithmetic.
+#
+# Also, we need to insert code in cut and paste operations to keep
+# track of various things when in limbo mode. Also need to catch
+# text deletions via typeover of selection.
+#
+ char := s[9];
+ if ( char == '\\' && len s > 10 )
+ char = s[10];
+ case char {
+ ' ' or '\t' =>
+ if ( ! in_comment )
+ look_keyword(this_word);
+ this_word = "" ;
+ '\n' =>
+ if ( in_comment ) {
+ # terminate current tag
+ cmd(ed, ".b.t tag remove comment insert-1chars");
+ in_comment = 0;
+ }
+ else
+ look_keyword(this_word);
+ this_word = "" ;
+ if ( last_kw_is_block )
+ indent = TEMP :: indent;
+ else while ( indent != nil && hd indent == TEMP )
+ indent = tl indent;
+ last_kw_is_block = 0;
+ add_indent();
+ first_char = 1;
+ return;
+ '{' =>
+ indent = BLOCK :: indent;
+ last_kw_is_block = 0;
+ '}' =>
+ if ( indent != nil )
+ indent = tl indent;
+ last_kw_is_block = 0;
+ # If the line is just indentation plus '}', rewrite it
+ # to have one less indent.
+ if ( first_char ) {
+ current := int cmd(ed, ".b.t index insert");
+ cmd(ed, ".b.t delete " +
+ string current + ".0 insert");
+ add_indent();
+ cmd(ed, ".b.t insert insert '}");
+ }
+# ';' =>
+# last_kw_is_block = 0;
+# '\b' => # By the time we see this, the character has
+# # already been wiped out, probably.
+# # To know what it was we'd need a lastchar,
+# # reset for each mouse button up and \b
+# '\u007f' => # Here, we have to know what used to be ahead of the
+# # insert point.
+ '#' =>
+ # if ( ! in_quote ) {
+ # cmd(ed, ".b.t tag add comment insert-1chars");
+ in_comment = 1;
+ # }
+ 'A' to 'Z' or 'a' to 'z' or '0' to '9' or '_' =>
+ if ( ! in_comment )
+ this_word[len this_word] = char;
+ * =>
+ if ( ! in_comment )
+ look_keyword(this_word);
+ this_word = "";
+ }
+ if ( in_comment )
+ cmd(ed, ".b.t tag add comment insert-1chars");
+ first_char = 0;
+}
+
+look_keyword(word : string)
+{
+ # compare this_word to all keywords
+ if ( is_keyword(word) ) {
+ cmd(ed, ".b.t tag add keyword insert-" +
+ string (len this_word + 1) + "chars insert-1chars");
+ }
+}
+
+is_keyword(word : string) : int
+{
+ l := len keywords;
+ for ( i := 0; i < l; i++ )
+ if ( word == keywords[i] ) {
+ if ( i != 26 ) # don't set for 'nil'
+ last_kw_is_block = int (block_keyword >> i) & 1;
+ return 1;
+ }
+ return 0;
+}
+
+do_new()
+{
+ cmd(ed, ".b.t delete 1.0 end");
+ curfile = "(New)";
+ tkclient->settitle(ed, "Edit " + curfile);
+}
+
+do_open(): int
+{
+ for(;;) {
+ fname := selectfile->filename(context, ed.image, "", nil, path);
+ if(fname == "")
+ break;
+ cmd(ed, ".b.t delete 1.0 end");
+ e := loadtfile(fname);
+ if(e == nil) {
+ basepath(fname);
+ return 1;
+ }
+
+ options := list of {
+ "Cancel",
+ "Open another file"
+ };
+
+ if(dialog->prompt(context, ed.image, ErrIco, "Open file", e, 0, options) == 0)
+ break;
+ }
+ return 0;
+}
+
+basepath(file: string)
+{
+ for(i := len file-1; i >= 0; i--)
+ if(file[i] == '/') {
+ path = file[0:i];
+ break;
+ }
+}
+
+do_save(prompt: int)
+{
+ fname := curfile;
+
+ contents := tk->cmd(ed, ".b.t get 1.0 end");
+ for(;;) {
+ if(prompt || curfile == "(New)") {
+ fname = dialog->getstring(context, ed.image, "File");
+ if ( len fname > 0 && fname[0] != '/' && path != "" )
+ fname = path + "/" + fname;
+ }
+
+ if(savetfile(fname, contents)) {
+ set_clean();
+ break;
+ }
+
+ options := list of {
+ "Cancel",
+ "Try another file"
+ };
+
+ msg := sys->sprint("Trying to write file \"%s\"\n%r", fname);
+ if(dialog->prompt(context, ed.image, ErrIco, "Save file", msg, 0, options) == 0)
+ break;
+
+ prompt = 1;
+ }
+}
+
+do_snarf(del: int)
+{
+ range := cmd(ed, ".b.t tag nextrange sel 1.0");
+ if(range == "" || (len range > 0 && range[0] == '!'))
+ return;
+ snarf = tk->cmd(ed, ".b.t get " + range);
+ if(del)
+ cmd(ed, ".b.t delete " + range);
+ tkclient->snarfput(snarf);
+}
+
+do_paste()
+{
+ snarf = tkclient->snarfget();
+ if(snarf == "")
+ return;
+ cmd(ed, ".b.t insert insert '" + snarf);
+}
+
+do_search(prompt: int) : int
+{
+ if(prompt == SEARCHFOR)
+ searchfor = dialog->getstring(context, ed.image, "Search For");
+ if(searchfor == "")
+ return 0;
+ cmd(ed, "cursor -bitmap cursor.wait");
+ ix := cmd(ed, ".b.t search -- " + tk->quote(searchfor) + " insert+1c");
+ if(ix != "" && len ix > 1 && ix[0] != '!') {
+ cmd(ed, ".b.t tag remove sel 0.0 end");
+ cmd(ed, ".b.t mark set anchor " + ix);
+ cmd(ed, ".b.t mark set insert " + ix);
+ cmd(ed, ".b.t tag add sel " + ix + " " + ix + "+" +
+ string(len searchfor) + "c");
+ if ( prompt != NOSEE )
+ cmd(ed, ".b.t see " + ix);
+ cmd(ed, "cursor -default");
+ return 1;
+ }
+ cmd(ed, "cursor -default");
+ return 0;
+}
+
+do_replace(prompt : int)
+{
+ range := "";
+ if ( prompt == REPLACEWITH ) {
+ replacewith = dialog->getstring(context, ed.image, "Replacement String");
+
+ range = cmd(ed, ".b.t tag nextrange sel 1.0");
+ if(range == "" || (len range > 0 && range[0] == '!'))
+ return; # nothing currently selected
+ }
+ if ( range != "" ) { # there's something selected
+ cmd(ed, ".b.t mark set insert sel.first");
+ }
+ else { # have to find a string
+ if ( searchfor == "" ) { # no search string!
+ if ( do_search(SEARCHFOR) == 0 )
+ return;
+ }
+ else if ( do_search(SEARCH) == 0 )
+ return;
+ }
+ cmd(ed, ".b.t delete sel.first sel.last");
+ cmd(ed, ".b.t insert insert " + tk->quote(replacewith));
+}
+
+do_replaceall()
+{
+ cur := cmd(ed, ".b.t index insert");
+ if ( cur == "" || cur[0] == '!' )
+ return;
+ dirt := 0;
+ if ( searchfor == "" ) # no search string
+ searchfor = dialog->getstring(context, ed.image, "Search For");
+ if ( searchfor == "" ) # still no search string
+ return;
+ srch := tk->quote(searchfor);
+ repl := tk->quote(replacewith);
+ for ( ix := "1.0"; len ix > 0 && ix[0] != '!'; ) {
+ ix = cmd(ed, ".b.t search -- " + srch + " " + ix + " end");
+ if ( ix == "" || len ix <= 1 || ix[0] == '!')
+ break;
+ cmd(ed, ".b.t delete " + ix + " " + ix + "+" +
+ string(len searchfor) + "c");
+ if ( replacewith != "" ) {
+ cmd(ed, ".b.t insert " + ix + " " + repl);
+ ix = cmd(ed, ".b.t index " + ix + "+" +
+ string(len replacewith) + "c");
+ }
+ dirt++;
+ }
+ cmd(ed, ".b.t mark set insert " + cur);
+ if ( dirt > 0 )
+ set_dirty();
+}
+
+
+loadtfile(path: string): string
+{
+ if ( path != nil && path[0] == '/' )
+ basepath(path);
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ return "Can't open "+path+", the error was:\n"+sys->sprint("%r");
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return "Can't stat "+path+", the error was:\n"+sys->sprint("%r");
+ if(d.mode & Sys->DMDIR)
+ return path+" is a directory";
+
+ cmd(ed, "cursor -bitmap cursor.wait");
+ BLEN: con 8192;
+ buf := array[BLEN+Sys->UTFmax] of byte;
+ inset := 0;
+ for(;;) {
+ n := sys->read(fd, buf[inset:], BLEN);
+ if(n <= 0)
+ break;
+ n += inset;
+ nutf := sys->utfbytes(buf, n);
+ s := string buf[0:nutf];
+ # move any partial rune to beginning of buffer
+ inset = n-nutf;
+ buf[0:] = buf[nutf:n];
+ cmd(ed, ".b.t insert end '" + s);
+ }
+ if ( is_limbo )
+ mark_keyw_comm();
+ curfile = path;
+ tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "cursor -default");
+ cmd(ed, "update");
+ return "";
+}
+
+savetfile(path: string, contents: string): int
+{
+ buf := array of byte contents;
+ n := len buf;
+
+ fd := sys->create(path, sys->OWRITE, 8r664);
+ if(fd == nil)
+ return 0;
+ i := sys->write(fd, buf, n);
+ if(i != n) {
+ sys->print("savetfile only wrote %d of %d: %r\n", i, n);
+ return 0;
+ }
+ curfile = path;
+# cmd(ed, ".m.filename configure -text '" + curfile);
+ tkclient->settitle(ed, "Edit " + curfile);
+
+ return 1;
+}
+
+mark_keyw_comm()
+{
+ quote := 0;
+ start : int;
+ notkey := 0;
+ word : string;
+
+ last := int cmd(ed, ".b.t index end");
+ for ( i := 1; i <= last; i++ ) {
+ quote = 0;
+ word = "";
+ line := tk->cmd(ed, ".b.t get " + string i + ".0 " +
+ string (i+1) + ".0");
+ l := len line;
+ll : for ( j := 0; j < l; j++ ) {
+ c := line[j];
+ if ( quote && (c = line[j]) != quote )
+ continue;
+ case c {
+ '#' =>
+ cmd(ed, sys->sprint(".b.t tag add comment" +
+ " %d.%d %d.%d", i, j, i, l));
+ break ll;
+ '\'' or '\"' =>
+ if ( j != 0 && line[j-1] == '\\' )
+ break;
+ if ( c == quote )
+ quote = 0;
+ else
+ quote = line[j];
+ word = "";
+ 'a' to 'z' =>
+ if ( word == "" )
+ start = j;
+ word[len word] = c;
+ 'A' to 'Z' or '_' =>
+ notkey = 1;
+ continue;
+ * =>
+ if ( ! notkey && is_keyword(word) )
+ cmd(ed, ".b.t tag add keyword " +
+ sys->sprint("%d.%d %d.%d",
+ i, start, i, j));
+ word = "";
+ notkey = 0;
+ }
+ }
+ }
+}
+
+do_indent()
+{
+ for ( ; ; ) {
+ tab = dialog->getstring(context, ed.image, "single indent");
+ break;
+ }
+ for ( i := 1; i <= 8; i++ ) {
+ s := "";
+ for ( j := i; j > 0; j-- )
+ s += tab;
+ tabs[i] = collapse(s);
+ }
+}
+
+collapse(s : string) : string
+{
+ if ( len s >= 8 && s[0:8] == " " )
+ return "\t" + collapse(s[8:]);
+ return s;
+}
+
+add_indent()
+{
+ for ( i := len indent; i >= 8; i -= 8 )
+ cmd(ed, ".b.t insert insert '" + tabs[8]);
+ cmd(ed, ".b.t insert insert '" + tabs[i]);
+}
+#
+# We should also look at the previous line, maybe.
+# And the line after. That may be too much.
+#
+# This is also the logical place to check if we are in a keyword,
+# reinitialize this_word (which presents problems if we are in the
+# middle of a word, etc.) Also check if we are in a comment or not.
+#
+re_indent()
+{
+ pos := cmd(ed, ".b.t index insert");
+ (n, lc) := sys->tokenize(pos, ".");
+ if ( n < 2 )
+ return;
+ init := tk->cmd(ed, ".b.t get " + hd lc + ".0 insert");
+ l := len init;
+ for ( i := 8; i > 0; i-- ) {
+ lt := len tabs[i];
+ if ( l >= lt && init[:lt] == tabs[i] )
+ break;
+ }
+ for ( indent = nil; len indent < i; indent = 0 :: indent) ;
+
+ in_comment = 0; # Are we in a comment?
+ for ( i = len tabs[i]; i < l; i++ )
+ if ( init[i] == '#' ) {
+ in_comment = 1;
+ break;
+ }
+}
+
+cmd(win: ref Tk->Toplevel, s: string): string
+{
+# sys->print("%s\n", s);
+ r := tk->cmd(win, s);
+ if (r != nil && r[0] == '!') {
+ sys->print("wm/edit: error executing '%s': %s\n", s, r);
+ }
+ return r;
+}