Added persistent option to EvMenu, making sure to safeguard against unsafe save states, which comes when trying to save un-picklable callables like methods and functions defined inside other functions.
This commit is contained in:
parent
83242408b0
commit
adc673f620
2 changed files with 129 additions and 48 deletions
|
|
@ -512,6 +512,22 @@ class CmdCreate(ObjManipCommand):
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
def _desc_load(caller):
|
||||||
|
return caller.db.evmenu_target.db.desc or ""
|
||||||
|
|
||||||
|
def _desc_save(caller, buf):
|
||||||
|
"""
|
||||||
|
Save line buffer to the desc prop. This should
|
||||||
|
return True if successful and also report its status to the user.
|
||||||
|
"""
|
||||||
|
caller.db.evmenu_target.db.desc = buf
|
||||||
|
caller.msg("Saved.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _desc_quit(caller):
|
||||||
|
caller.attributes.remove("evmenu_target")
|
||||||
|
caller.msg("Exited editor.")
|
||||||
|
|
||||||
class CmdDesc(MuxCommand):
|
class CmdDesc(MuxCommand):
|
||||||
"""
|
"""
|
||||||
describe an object
|
describe an object
|
||||||
|
|
@ -543,19 +559,24 @@ class CmdDesc(MuxCommand):
|
||||||
return
|
return
|
||||||
|
|
||||||
def load(caller):
|
def load(caller):
|
||||||
return obj.db.desc or ""
|
return caller.db.evmenu_target.db.desc or ""
|
||||||
|
|
||||||
def save(caller, buf):
|
def save(caller, buf):
|
||||||
"""
|
"""
|
||||||
Save line buffer to the desc prop. This should
|
Save line buffer to the desc prop. This should
|
||||||
return True if successful and also report its status to the user.
|
return True if successful and also report its status to the user.
|
||||||
"""
|
"""
|
||||||
obj.db.desc = buf
|
caller.db.evmenu_target.db.desc = buf
|
||||||
caller.msg("Saved.")
|
caller.msg("Saved.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def quit(caller):
|
||||||
|
caller.attributes.remove("evmenu_target")
|
||||||
|
caller.msg("Exited editor.")
|
||||||
|
|
||||||
|
self.caller.db.evmenu_target = obj
|
||||||
# launch the editor
|
# launch the editor
|
||||||
EvEditor(self.caller, loadfunc=load, savefunc=save, key="desc")
|
EvEditor(self.caller, loadfunc=_desc_load, savefunc=_desc_save, quitfunc=_desc_quit, key="desc", persistent=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,10 @@ and initialize it:
|
||||||
from builtins import object
|
from builtins import object
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import inspect
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia import Command, CmdSet
|
from evennia import Command, CmdSet
|
||||||
from evennia.utils import is_iter, fill, dedent
|
from evennia.utils import is_iter, fill, dedent, logger
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
|
|
||||||
# we use cmdhandler instead of evennia.syscmdkeys to
|
# we use cmdhandler instead of evennia.syscmdkeys to
|
||||||
|
|
@ -59,9 +60,9 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
_HELP_TEXT = \
|
_HELP_TEXT = \
|
||||||
"""
|
"""
|
||||||
<txt> - any non-command is appended to the end of the buffer.
|
<txt> - any non-command is appended to the end of the buffer.
|
||||||
: <l> - view buffer or only line <l>
|
: <l> - view buffer or only line(s) <l>
|
||||||
:: <l> - view buffer without line numbers or other parsing
|
:: <l> - raw-view buffer or only line(s) <l>
|
||||||
::: - print a ':' as the only character on the line...
|
::: - escape - enter ':' as the only character on the line.
|
||||||
:h - this help.
|
:h - this help.
|
||||||
|
|
||||||
:w - save the buffer (don't quit)
|
:w - save the buffer (don't quit)
|
||||||
|
|
@ -73,13 +74,13 @@ _HELP_TEXT = \
|
||||||
:uu - (redo) step forward in undo history
|
:uu - (redo) step forward in undo history
|
||||||
:UU - reset all changes back to initial state
|
:UU - reset all changes back to initial state
|
||||||
|
|
||||||
:dd <l> - delete line <n>
|
:dd <l> - delete last line or line(s) <l>
|
||||||
:dw <l> <w> - delete word or regex <w> in entire buffer or on line <l>
|
:dw <l> <w> - delete word or regex <w> in entire buffer or on line <l>
|
||||||
:DD - clear buffer
|
:DD - clear entire buffer
|
||||||
|
|
||||||
:y <l> - yank (copy) line <l> to the copy buffer
|
:y <l> - yank (copy) line(s) <l> to the copy buffer
|
||||||
:x <l> - cut line <l> and store it in the copy buffer
|
:x <l> - cut line(s) <l> and store it in the copy buffer
|
||||||
:p <l> - put (paste) previously copied line directly after <l>
|
:p <l> - put (paste) previously copied line(s) directly after <l>
|
||||||
:i <l> <txt> - insert new text <txt> at line <l>. Old line will move down
|
:i <l> <txt> - insert new text <txt> at line <l>. Old line will move down
|
||||||
:r <l> <txt> - replace line <l> with text <txt>
|
:r <l> <txt> - replace line <l> with text <txt>
|
||||||
:I <l> <txt> - insert text at the beginning of line <l>
|
:I <l> <txt> - insert text at the beginning of line <l>
|
||||||
|
|
@ -94,26 +95,26 @@ _HELP_TEXT = \
|
||||||
:echo - turn echoing of the input on/off (helpful for some clients)
|
:echo - turn echoing of the input on/off (helpful for some clients)
|
||||||
|
|
||||||
Legend:
|
Legend:
|
||||||
<l> - line numbers, or range lstart:lend, e.g. '3:7'.
|
<l> - line number, like '5' or range, like '3:7'.
|
||||||
<w> - one word or several enclosed in quotes.
|
<w> - a single word, or multiple words with quotes around them.
|
||||||
<txt> - longer string, usually not needed to be enclosed in quotes.
|
<txt> - longer string, usually not needing quotes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ERROR_LOADFUNC = \
|
_ERROR_LOADFUNC = \
|
||||||
"""
|
"""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
{rBuffer load function error. Could not load initial data.{n
|
|rBuffer load function error. Could not load initial data.|n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ERROR_SAVEFUNC = \
|
_ERROR_SAVEFUNC = \
|
||||||
"""
|
"""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
{rSave function returned an error. Buffer not saved.{n
|
|rSave function returned an error. Buffer not saved.|n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ERROR_NO_SAVEFUNC = "{rNo save function defined. Buffer cannot be saved.{n"
|
_ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n"
|
||||||
|
|
||||||
_MSG_SAVE_NO_CHANGE = "No changes need saving"
|
_MSG_SAVE_NO_CHANGE = "No changes need saving"
|
||||||
_DEFAULT_NO_QUITFUNC = "Exited editor."
|
_DEFAULT_NO_QUITFUNC = "Exited editor."
|
||||||
|
|
@ -122,11 +123,21 @@ _ERROR_QUITFUNC = \
|
||||||
"""
|
"""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
{rQuit function gave an error. Skipping.{n
|
|rQuit function gave an error. Skipping.|n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_MSG_NO_UNDO = "Nothing to undo"
|
_ERROR_PERSISTENT_SAVING = \
|
||||||
_MSG_NO_REDO = "Nothing to redo"
|
"""
|
||||||
|
{error}
|
||||||
|
|
||||||
|
|rThe editor state could not be saved for persistent mode. Switching
|
||||||
|
to non-persistent mode (which means the editor session won't survive
|
||||||
|
an eventual server reload - so save often!)|n
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_MSG_NO_UNDO = "Nothing to undo."
|
||||||
|
_MSG_NO_REDO = "Nothing to redo."
|
||||||
_MSG_UNDO = "Undid one step."
|
_MSG_UNDO = "Undid one step."
|
||||||
_MSG_REDO = "Redid one step."
|
_MSG_REDO = "Redid one step."
|
||||||
|
|
||||||
|
|
@ -155,11 +166,11 @@ class CmdSaveYesNo(Command):
|
||||||
self.caller.cmdset.remove(SaveYesNoCmdSet)
|
self.caller.cmdset.remove(SaveYesNoCmdSet)
|
||||||
if self.raw_string.strip().lower() in ("no", "n"):
|
if self.raw_string.strip().lower() in ("no", "n"):
|
||||||
# answered no
|
# answered no
|
||||||
self.caller.msg(self.caller.ndb._lineeditor.quit())
|
self.caller.msg(self.caller.ndb._eveditor.quit())
|
||||||
else:
|
else:
|
||||||
# answered yes (default)
|
# answered yes (default)
|
||||||
self.caller.ndb._lineeditor.save_buffer()
|
self.caller.ndb._eveditor.save_buffer()
|
||||||
self.caller.ndb._lineeditor.quit()
|
self.caller.ndb._eveditor.quit()
|
||||||
|
|
||||||
|
|
||||||
class SaveYesNoCmdSet(CmdSet):
|
class SaveYesNoCmdSet(CmdSet):
|
||||||
|
|
@ -204,8 +215,15 @@ class CmdEditorBase(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
linebuffer = []
|
linebuffer = []
|
||||||
if self.editor:
|
editor = self.caller.ndb._eveditor
|
||||||
|
if not editor:
|
||||||
|
# this will completely replace the editor
|
||||||
|
_load_editor(self.caller)
|
||||||
|
editor = self.caller.ndb._eveditor
|
||||||
|
self.editor = editor
|
||||||
|
|
||||||
linebuffer = self.editor.get_buffer().split("\n")
|
linebuffer = self.editor.get_buffer().split("\n")
|
||||||
|
|
||||||
nlines = len(linebuffer)
|
nlines = len(linebuffer)
|
||||||
|
|
||||||
# The regular expression will split the line by whitespaces,
|
# The regular expression will split the line by whitespaces,
|
||||||
|
|
@ -285,6 +303,27 @@ class CmdEditorBase(Command):
|
||||||
self.arg2 = arg2
|
self.arg2 = arg2
|
||||||
|
|
||||||
|
|
||||||
|
def _load_editor(caller):
|
||||||
|
"""
|
||||||
|
Load persistent editor from storage.
|
||||||
|
"""
|
||||||
|
saved_options = caller.attributes.get("_eveditor_saved")
|
||||||
|
saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None))
|
||||||
|
if saved_options:
|
||||||
|
eveditor = EvEditor(caller, **saved_options[0])
|
||||||
|
if saved_buffer:
|
||||||
|
# we have to re-save the buffer data so we can handle subsequent restarts
|
||||||
|
caller.attributes.add("_eveditor_buffer_temp", (saved_buffer, saved_undo))
|
||||||
|
setattr(eveditor, "_buffer", saved_buffer)
|
||||||
|
setattr(eveditor, "_undo_buffer", saved_undo)
|
||||||
|
setattr(eveditor, "_undo_pos", len(saved_undo) - 1)
|
||||||
|
for key, value in saved_options[1].iteritems():
|
||||||
|
setattr(eveditor, key, value)
|
||||||
|
else:
|
||||||
|
# something went wrong. Cleanup.
|
||||||
|
caller.cmdset.remove(EvEditorCmdSet)
|
||||||
|
|
||||||
|
|
||||||
class CmdLineInput(CmdEditorBase):
|
class CmdLineInput(CmdEditorBase):
|
||||||
"""
|
"""
|
||||||
No command match - Inputs line of text into buffer.
|
No command match - Inputs line of text into buffer.
|
||||||
|
|
@ -296,7 +335,9 @@ class CmdLineInput(CmdEditorBase):
|
||||||
"""
|
"""
|
||||||
Adds the line without any formatting changes.
|
Adds the line without any formatting changes.
|
||||||
"""
|
"""
|
||||||
editor = self.editor
|
caller = self.caller
|
||||||
|
editor = caller.ndb._eveditor
|
||||||
|
|
||||||
buf = editor.get_buffer()
|
buf = editor.get_buffer()
|
||||||
|
|
||||||
# add a line of text to buffer
|
# add a line of text to buffer
|
||||||
|
|
@ -328,7 +369,8 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
efficient presentation.
|
efficient presentation.
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
editor = self.editor
|
editor = caller.ndb._eveditor
|
||||||
|
|
||||||
linebuffer = self.linebuffer
|
linebuffer = self.linebuffer
|
||||||
lstart, lend = self.lstart, self.lend
|
lstart, lend = self.lstart, self.lend
|
||||||
cmd = self.cmdstring
|
cmd = self.cmdstring
|
||||||
|
|
@ -532,6 +574,9 @@ class EvEditorCmdSet(CmdSet):
|
||||||
"CmdSet for the editor commands"
|
"CmdSet for the editor commands"
|
||||||
key = "editorcmdset"
|
key = "editorcmdset"
|
||||||
mergetype = "Replace"
|
mergetype = "Replace"
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
self.add(CmdLineInput())
|
||||||
|
self.add(CmdEditorGroup())
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
@ -548,7 +593,7 @@ class EvEditor(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, caller, loadfunc=None, savefunc=None,
|
def __init__(self, caller, loadfunc=None, savefunc=None,
|
||||||
quitfunc=None, key=""):
|
quitfunc=None, key="", persistent=False):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
caller (Object): Who is using the editor.
|
caller (Object): Who is using the editor.
|
||||||
|
|
@ -568,13 +613,17 @@ class EvEditor(object):
|
||||||
supply to `quitfunc`.
|
supply to `quitfunc`.
|
||||||
key (str, optional): An optional key for naming this
|
key (str, optional): An optional key for naming this
|
||||||
session and make it unique from other editing sessions.
|
session and make it unique from other editing sessions.
|
||||||
|
persistent (bool, optional): Make the editor survive a reboot. Note
|
||||||
|
that if this is set, all callables must be functions (not methods)
|
||||||
|
since they have to able to pickle.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._key = key
|
self._key = key
|
||||||
self._caller = caller
|
self._caller = caller
|
||||||
self._caller.ndb._lineeditor = self
|
self._caller.ndb._eveditor = self
|
||||||
self._buffer = ""
|
self._buffer = ""
|
||||||
self._unsaved = False
|
self._unsaved = False
|
||||||
|
self._persistent = persistent
|
||||||
|
|
||||||
if loadfunc:
|
if loadfunc:
|
||||||
self._loadfunc = loadfunc
|
self._loadfunc = loadfunc
|
||||||
|
|
@ -590,19 +639,6 @@ class EvEditor(object):
|
||||||
else:
|
else:
|
||||||
self._quitfunc = lambda caller: caller.msg(_DEFAULT_NO_QUITFUNC)
|
self._quitfunc = lambda caller: caller.msg(_DEFAULT_NO_QUITFUNC)
|
||||||
|
|
||||||
# Create the commands we need
|
|
||||||
cmd1 = CmdLineInput()
|
|
||||||
cmd1.editor = self
|
|
||||||
cmd1.obj = self
|
|
||||||
cmd2 = CmdEditorGroup()
|
|
||||||
cmd2.obj = self
|
|
||||||
cmd2.editor = self
|
|
||||||
# Populate cmdset and add it to caller
|
|
||||||
editor_cmdset = EvEditorCmdSet()
|
|
||||||
editor_cmdset.add(cmd1)
|
|
||||||
editor_cmdset.add(cmd2)
|
|
||||||
self._caller.cmdset.add(editor_cmdset)
|
|
||||||
|
|
||||||
# store the original version
|
# store the original version
|
||||||
self._pristine_buffer = self._buffer
|
self._pristine_buffer = self._buffer
|
||||||
self._sep = "-"
|
self._sep = "-"
|
||||||
|
|
@ -615,6 +651,25 @@ class EvEditor(object):
|
||||||
# copy buffer
|
# copy buffer
|
||||||
self._copy_buffer = []
|
self._copy_buffer = []
|
||||||
|
|
||||||
|
if persistent:
|
||||||
|
# save in tuple {kwargs, other options}
|
||||||
|
try:
|
||||||
|
caller.attributes.add("_eveditor_saved",(
|
||||||
|
{"loadfunc":loadfunc, "savefunc": savefunc,
|
||||||
|
"quitfunc": quitfunc, "key": key, "persistent": persistent},
|
||||||
|
{"_pristine_buffer": self._pristine_buffer,
|
||||||
|
"_sep": self._sep}))
|
||||||
|
caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer))
|
||||||
|
except Exception, err:
|
||||||
|
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err))
|
||||||
|
logger.log_trace("EvEditor persistent-mode error. Commonly, this is because one or "\
|
||||||
|
"more of the EvEditor callbacks could not be pickled, for example because it's "\
|
||||||
|
"a class method or is defined inside another function.")
|
||||||
|
persistent = False
|
||||||
|
|
||||||
|
# Create the commands we need
|
||||||
|
caller.cmdset.add(EvEditorCmdSet, permanent=persistent)
|
||||||
|
|
||||||
# echo inserted text back to caller
|
# echo inserted text back to caller
|
||||||
self._echo_mode = True
|
self._echo_mode = True
|
||||||
|
|
||||||
|
|
@ -628,6 +683,8 @@ class EvEditor(object):
|
||||||
try:
|
try:
|
||||||
self._buffer = self._loadfunc(self._caller)
|
self._buffer = self._loadfunc(self._caller)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
from evennia.utils import logger
|
||||||
|
logger.log_trace()
|
||||||
self._caller.msg(_ERROR_LOADFUNC.format(error=e))
|
self._caller.msg(_ERROR_LOADFUNC.format(error=e))
|
||||||
|
|
||||||
def get_buffer(self):
|
def get_buffer(self):
|
||||||
|
|
@ -654,6 +711,8 @@ class EvEditor(object):
|
||||||
self._buffer = buf
|
self._buffer = buf
|
||||||
self.update_undo()
|
self.update_undo()
|
||||||
self._unsaved = True
|
self._unsaved = True
|
||||||
|
if self._persistent:
|
||||||
|
self._caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer))
|
||||||
|
|
||||||
def quit(self):
|
def quit(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -664,13 +723,14 @@ class EvEditor(object):
|
||||||
self._quitfunc(self._caller)
|
self._quitfunc(self._caller)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._caller.msg(_ERROR_QUITFUNC.format(error=e))
|
self._caller.msg(_ERROR_QUITFUNC.format(error=e))
|
||||||
del self._caller.ndb._lineeditor
|
self._caller.nattributes.remove("_eveditor")
|
||||||
|
self._caller.attributes.remove("_eveditor_buffer_temp")
|
||||||
|
self._caller.attributes.remove("_eveditor_saved")
|
||||||
self._caller.cmdset.remove(EvEditorCmdSet)
|
self._caller.cmdset.remove(EvEditorCmdSet)
|
||||||
|
|
||||||
def save_buffer(self):
|
def save_buffer(self):
|
||||||
"""
|
"""
|
||||||
Saves the content of the buffer. The 'quitting' argument is a bool
|
Saves the content of the buffer.
|
||||||
indicating whether or not the editor intends to exit after saving.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self._unsaved:
|
if self._unsaved:
|
||||||
|
|
@ -742,8 +802,8 @@ class EvEditor(object):
|
||||||
nchars = len(buf)
|
nchars = len(buf)
|
||||||
|
|
||||||
sep = self._sep
|
sep = self._sep
|
||||||
header = "{n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-25-len(self._key))
|
header = "|n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-25-len(self._key))
|
||||||
footer = "{n" + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) \
|
footer = "|n" + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) \
|
||||||
+ sep * 12 + "(:h for help)" + sep * 23
|
+ sep * 12 + "(:h for help)" + sep * 23
|
||||||
if linenums:
|
if linenums:
|
||||||
main = "\n".join("{b%02i|{n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
|
main = "\n".join("{b%02i|{n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue