Core Utility Help

$generic_editor

The Generic Editor enables a player to edit a list of strings. While one might contrive to use it directly, it is rather intended as a parent for some actual editor. It supplies the following commands:

say w*hat

emote abort

lis*t [] [nonum] q*uit,done,pause

ins*ert [] ["]

n*ext,p*rev [n] ["]

del*ete []

f*ind /[/[c][]]

s*ubst //[/[g][c][]]

m*ove,c*opy [] to

join*l []

fill [] [@]

$editor_help.(cmdname) descrbes cmdname

$editor_help.insert descrbes insertion points ()

$editor_help.ranges descrbes range specifications ()

You'll notice that nowhere does it say how to load in a given list of strings or how and where one may save said list away when one is done editing. These commands are supplied by the child editor object. The generic editor contains only the code for editing lines, though it defines additional functions for use by the children:

:loaded(player)

returns the index (player in this.active) iff text has been loaded

from somewhere, otherwise returns 0.

Note that, by default, there is a difference between

having nothing loaded (:text(who)==0) and

having loaded something with no text (:text(who)=={}).

If you don't care about this distinction in a particular case,

just do (player in this.active) instead of this:loaded(player).

If you don't want your editor to make this distinction at all, do

@stateprop texts={} for

which changes the initial value of :text() to {}

In all functions below, 'who' is the index returned by :loaded(player)

BTW, be careful about using 'player' in non-user (i.e., +x this-none-this) verbs --- much better to have the user verb get the index with :loaded() and then pass that around.

Also be careful about suspend() and verbs that call suspend(). In particular, the player's index in the .active list can change during the suspend interval, so you must be sure to obtain the index (e.g., using :loaded()) again after the suspend() returns.

For your non-user verbs, we have

:ok(who)

returns E_PERM if the caller is not an editor verb and E_RANGE

if 'who' does not point to a valid session.

which should take care of the more egregious security holes (but maybe not the less egregious ones). For getting and loading text, we have

:text(who)

the current text string list or 0 if nothing loaded yet.

:load(who,text)

loads the given list of strings as the text to be edited.

this also resets the 'changed' flag and pushes the insertion

point to the end.

and various flags and properties (all of the set_* routines return E_PERM when not called from an editor verb, E_RANGE if who is out of bounds, E_INVARG if something is wrong with the 2nd arg, or the new value, which may not necessarily be the same as the 2nd arg (e.g., set_insertion(..,37) on a 5 line text buffer returns 6).

:changed(who)

has the text been altered since the last save/load?

(the child editor gets to define what "save" means).

:set_changed(who,value)

Any child editor command that is considered to save the text should do a

:set_changed(who,0).

Note that if the changed flag is 0, the session will be flushed when

the player leaves the editor, so you may also want certain commands to

do set_changed(who,1)...

:origin(who)

room where the player came from.

:set_origin(who,room)

can be used to change the room the player will return to when finished

editing. Since origin gets set even in cases where the player teleports

into the editor you probably won't usually need to do this.

:insertion(who)

current insertion point.

:set_insertion(who,linenumber)

linenumber needs to be a positive integer and will get

:readable(who)

whether the current editing session has been made globally readable.

:set_readable(who,boolean)

change the readability of the current editing session.

This is used by the publish/perish verbs.

We also provide

:invoke(...)

If the player has a previous unsaved (i.e., :changed()!=0)

session, we return to it, moving the player to the editor.

If the player is already in the editor, this has no effect other

than to print a few nasty messages. In any case a :changed()

session must be aborted or set_changed(,0) before anything else

can be started

Otherwise, we pass the arguments (which are assumed to be the

result of some munging of the command line) to :parse_invoke(),

move the player to the editor and load whatever parse_invoke()

specified. The only interpretation the generic editor makes on

the arguments is that if the boolean value of the first is true,

this indicates that the player wanted to load something as

opposed to resume a previous session. Usually a command calling

:invoke will have a true (i.e., nonzero number, nonempty list or

string) first arg iff the command line is nonempty, in which case

'args' works fine for this purpose.

If the command parses sucessfully (:parse_invoke() returns a list),

we move the player to the editor if necessary and then call

:init_session() to set things up.

The child editor is assumed to provide

:parse_invoke(...)

given :invoke()'s arguments, determines what the player wants to edit.

It either returns 0 and reports syntax errors to player,

or it returns some list that :init_session() will understand.

:init_session(who,@spec)

where spec is something that was returned by :parse_invoke().

Loads the text and sets the stateprops (below) to indicate that

we are working on whatever it is we're suppose to be working on.

:working_on(who)

returns a string X as in "You are working on X."

This is called by the 'w*hat' command, among other things.

Child editors may have their own properties giving state information for the various editing sessions. The value of each such property will be a list giving a value for each player in the editor. For each such property, you should, once the editor object has been created, initialize the property to {} and do one of

@stateprop for

@stateprop = for

(0 is the default )

Henceforth, adding and deleting new editing sessions will amend the list held by the given property. The value of the property for a given session can be obtained via this.[player in this.active] and can be changed with a corresponding listset() call. The usual idiom for an editor command is

if(!(who=this:loaded(player)))

player:tell(nothing_loaded_msg());

else

... various references to this.[who] ...

endif

To remove such a property from the list of such state properties:

@rmstateprop from

Note that you can only do this with properties defined on the child editor itself.

Sometimes you may wish to @stateprop a new property on an editor where active editing sessions exist. @stateprop will fail if the property in question does not hold a list of the correct length (== length(editor.active); one value for each editing session). You need to either give the @flush command to clear out all sessions and boot all players currently in the editor or somehow manually initialize the property to a list of appropriate values and pray that nobody enters/exits the editor between the property initialization and the @stateprop command --- this problem can be avoided by doing an eval() that does all of the initializations (beware of suspends()) and calls :set_stateprops directly.

Incidentally, the @flush command may be used at any time to clean out the editor or to remove all sessions older than a given date.

There are also numerous _msg properties that may be customized

@depart announced at the origin when :invoke() is called.

@return announced at the origin the player is returned there.

@nothing_loaded printed when user attempts editing

before anything has been loaded.

@no_text response to 'list' when :text()=={}

@no_change printed by 'what' when :changed()==0

@change printed by 'what' when :changed()==1

@no_littering printed upon leaving the editor with :changed()==1.

@previous_session printed by :invoke() when player tries to start a

new session without aborting or saving the old one

The general procedure for creating a child editor:

. @create $generic_editor named

. define additional verbs/properties

At the very least you need 'edit' and 'save' commands.

Usually you can get away with just having 'edit' call :invoke();

Presumably, you'll need at least a command to load text from somewhere

as well as a command to save it back out.

. define a verb (somewhere) to invoke the editor

This could be just a one-liner that calls :invoke(args,verb).

Either that or

. you have to set up an exit somewhere whose destination is

. you have to advertise the object number so that people can

teleport to it.

. @stateprop x for

. if you want the 'abort' command to boot the player from the editor do

.exit_on_abort = 1;

. set .commands to be the list of additional commands defined

by .

Each element of the list is itself a list of the form {name,args}.

set .commands2 to be the list of commands that should appear

in the `look' listing, and should be a list of strings appearing

as names in .commands on either or some editor ancestor.

look at $verb_editor or $note_editor for an example.

. If you want to have help text for new verbs you define, create a child of

$generic_help and add properties to this object for each of the topics

that you want to provide help text.

Finally, set .help = {this object} so that the help system

knows to consult this object.