Support for Local Editing of MOO Text ------------------------------------- by Haakon of LambdaMOO The MOO code in this file installs support for local editing of various pieces of MOO text, such as the text of a note, the code of a verb, or the description of some object. The support relies on your MUD client understanding a simple protocol for downloading and uploading text. A version of the GNU Emacs MUD client `mud.el' that understands this protocol is available for anonymous FTP from parcftp.xerox.com:pub/MOO/clients. The client's perspective ------------------------ When the MOO wishes to invoke local editing of some piece of text, it prints output like the following to the player's connection: #$# edit name: upload: ... zero or more lines of dot-quoted text ... . is a good human-readable name for the text to be edited. Your client's local editor should use it for some name that the user sees attached to the text during editing, such as the name of an editor buffer. is a MOO command that your client should use to send the edited text back to the server. When the user is done editing, the client should send output like the following to the server: ... zero or more lines of dot-quoted text ... . `Dot-quoted' text has had an extra dot (`.') prepended to every line that began with a dot; to unquote it, your client should simply change every initial `..' in the lines to `.'. The MOO user's perspective -------------------------- To enable local editing, you should turn on the `local' edit-option, like this: @edit-option +local From then on, whenever you use an editing command that supports local editing, your client will automagically provide you with an editor on the text in question. When you save your edits (however you do that in your editor), the changed text will automagically make its way back to the MOO and you'll see a message indicating whether or not the save was successful. (Saves can fail if, for example, your verb code is syntactically invalid or you do not have permission to change the property whose value you were editing.) Some of the standard MOO editors may not support local editing; if this is the case, then you will be placed into the usual MOO line-based editor as if you had not enabled local editing. The wizard's perspective ------------------------ The enclosed code was developed and tested on a database derived from the 28 October 1992 release of LambdaCore.db; your mileage may vary. It lays down a simple mechanism whereby children of $generic_editor can signal their willingness to support local editing. It can be quite easy to add support for a new kind of editor. After the enclosed code is installed, the generic editor checks on every invocation whether or not both the user and the invoked editor support local editing. For the user, this is specified with the `local' edit-option. For the editor, it should define a verb named `local_editing_info' which is called like this: editor:local_editing_info(@spec) where `spec' is a value returned by editor:parse_invoke(). If the editor can support local editing of the specified text, it should return a list of three elements: {name, text, upload} where `name' is a good human-readable name for the text to be edited, `text' is a string or list of strings (the text to be edited), and `upload' is a string giving a MOO command that the client can use to send the text back. That command should be available to all users (*not* just programmers), should call $command_utils:read_lines() to get the newly-edited text (as a list of strings), and should attempt to save that text in the appropriate place, using the permissions of the user. The enclosed code adds the `local' edit-option, the generic editor's check for the user and the editor having enabled local editing, and code necessary to make the LambdaCore note editor and verb editor (i.e., the @notedit and @edit commands) support local editing. I have not added support yet for the mail editor (it was too much work to make it handle the headers properly and all of the myriad ways that the mail editor can be invoked) or for local editing of `help' texts (this appears to require some additions to the help system's internal protocols and I wasn't up to that when I wrote this). The last four lines of my $generic_editor:invoke look like this: spec = this:parse_invoke(@args); if (typeof(spec) == LIST && this:suck_in(player)) this:init_session(player in this.active, @spec); endif I changed them to the following and you should do something similar: spec = this:parse_invoke(@args); if (typeof(spec) == LIST) if (player:edit_option("local") && $object_utils:has_verb(this, "local_editing_info") && (info = this:local_editing_info(@spec))) this:invoke_local_editor(@info); elseif (this:suck_in(player)) this:init_session(player in this.active, @spec); endif endif The remainder of this file is a downloadable script for the rest of the installation. @prop $edit_options.show_local ;$edit_options.show_local = {"Use in-MOO text editors.", "Ship text to client for local editing."} ;$edit_options:add_name("local") @verb $generic_editor:invoke_local_editor this none this rxd @program $generic_editor:invoke_local_editor ":invoke_local_editor(name, text, upload)"; "Spits out the magic text that invokes the local editor in the player's client."; "NAME is a good human-readable name for the local editor to use for this particular piece of text."; "TEXT is a string or list of strings, the initial body of the text being edited."; "UPLOAD, a string, is a MOO command that the local editor can use to save the text when the user is done editing. The local editor is going to send that command on a line by itself, followed by the new text lines, followed by a line containing only `.'. The UPLOAD command should therefore call $command_utils:read_lines() to get the new text as a list of strings."; if (caller != this) return; endif name = args[1]; text = args[2]; upload = args[3]; if (typeof(text) == STR) text = {text}; endif notify(player, tostr("#$# edit name: ", name, " upload: ", upload)); ":dump_lines() takes care of the final `.' ..."; for line in ($command_utils:dump_lines(text)) notify(player, line); endfor . @verb $note_editor:local_editing_info this none this rxd $hacker @program $note_editor:local_editing_info what = args[1]; text = args[2]; cmd = typeof(text) == STR ? "@set-note-string" | "@set-note-text"; name = typeof(what) == OBJ ? what.name | tostr(what[1].name, ".", what[2]); note = typeof(what) == OBJ ? what | tostr(what[1], ".", what[2]); return {name, text, tostr(cmd, " ", note)}; . @verb $player:"@set-note-string @set-note-text" any none none r @program $player:@set-note-string "Usage: @set-note-{string | text} {#xx | #xx.pname}"; " ...lines of text..."; " ."; ""; "For use by clients' local editors, to save new text for a note or object property. See $note_editor:local_editing_info() for details."; set_task_perms(player); text = $command_utils:read_lines(1); if (verb == "@set-note-string" && length(text) <= 1) text = text ? text[1] | ""; endif if (spec = $code_utils:parse_propref(argstr)) o = toobj(spec[1]); p = spec[2]; e = o.(p) = text; if (e != text) player:tell("Error: ", e); else player:tell("Set ", p, " property of ", o.name, " (", o, ")."); endif elseif (typeof(note = $code_utils:toobj(argstr)) == OBJ) e = note:set_text(text); if (typeof(e) == ERR) player:tell("Error: ", e); else player:tell("Set text of ", o.name, " (", o, ")."); endif else player:tell("Error: Malformed argument to ", verb, ": ", argstr); endif . @verb $verb_editor:local_editing_info this none this "rdx" $hacker @program $verb_editor:local_editing_info object = args[1]; vname = args[2]; code = args[3]; name = tostr(object.name, ":", vname); return {name, code, tostr("@program ", object, ":", vname)}; .