Persistent-selection (and fix bug 3162)

* src/CutAndPaste.h/C: add selectionBuffer to save selected text
	* src/text3.C: proper handling of paste
	* src/lyxfind.C: save selection
	* src/BufferView.C: save selection
	* src/text.C: save selection
	* src/cursor.C: save selection
	* src/insets/insettabular.C: save selection
	* src/mathed/InsetMathGrid.C: save selection
	* src/mathed/InsetMathHull.C: save selection
	* src/mathed/InsetMathNest.C: save selection


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@17022 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Bo Peng 2007-02-02 03:10:15 +00:00
parent f44bc0a0d1
commit 8b56166641
12 changed files with 167 additions and 80 deletions

View File

@ -63,7 +63,6 @@
#include "frontends/Alert.h"
#include "frontends/FileDialog.h"
#include "frontends/FontMetrics.h"
#include "frontends/Selection.h"
#include "graphics/Previews.h"
@ -209,7 +208,8 @@ void BufferView::setBuffer(Buffer * b)
cursor_.resetAnchor();
cursor_.setCursor(buffer_->getCursor().asDocIterator(&(buffer_->inset())));
cursor_.setSelection();
theSelection().haveSelection(cursor_.selection());
// do not set selection to the new buffer because we
// only paste recent selection.
}
}
@ -1054,6 +1054,10 @@ void BufferView::clearSelection()
{
if (buffer_) {
cursor_.clearSelection();
// Clear the selection buffer. Otherwise a subsequent
// middle-mouse-button paste would use the selection buffer,
// not the more current external selection.
cap::clearSelection();
xsel_cache_.set = false;
// The buffer did not really change, but this causes the
// redraw we need because we cleared the selection above.
@ -1342,7 +1346,7 @@ void BufferView::putSelectionAt(DocIterator const & cur,
cursor_.setSelection(cursor_, -length);
} else
cursor_.setSelection(cursor_, length);
theSelection().haveSelection(cursor_.selection());
cap::saveSelection(cursor_);
}
}

View File

@ -73,6 +73,8 @@ typedef std::pair<pit_type, int> PitPosPair;
typedef limited_stack<pair<ParagraphList, textclass_type> > CutStack;
CutStack theCuts(10);
// persistent selection, cleared until the next selection
CutStack selectionBuffer(1);
// store whether the tabular stack is newer than the normal copy stack
// FIXME: this is a workaround for bug 1919. Should be removed for 1.5,
@ -343,7 +345,7 @@ void putClipboard(ParagraphList const & paragraphs, textclass_type textclass,
void copySelectionHelper(Buffer const & buf, ParagraphList & pars,
pit_type startpit, pit_type endpit,
int start, int end, textclass_type tc)
int start, int end, textclass_type tc, CutStack & cutstack)
{
BOOST_ASSERT(0 <= start && start <= pars[startpit].size());
BOOST_ASSERT(0 <= end && end <= pars[endpit].size());
@ -377,7 +379,7 @@ void copySelectionHelper(Buffer const & buf, ParagraphList & pars,
// again, do not track deletion
front.eraseChars(0, start, false);
theCuts.push(make_pair(paragraphs, tc));
cutstack.push(make_pair(paragraphs, tc));
}
} // namespace anon
@ -524,7 +526,7 @@ void cutSelection(LCursor & cur, bool doclear, bool realcut)
text->paragraphs(),
begpit, endpit,
cur.selBegin().pos(), endpos,
bp.textclass);
bp.textclass, theCuts);
// Stuff what we got on the clipboard.
// Even if there is no selection.
putClipboard(theCuts[0].first, theCuts[0].second,
@ -580,16 +582,9 @@ void copySelection(LCursor & cur)
}
void copySelection(LCursor & cur, docstring const & plaintext)
{
copySelectionToStack(cur);
namespace {
// stuff the selection onto the X clipboard, from an explicit copy request
putClipboard(theCuts[0].first, theCuts[0].second, plaintext);
}
void copySelectionToStack(LCursor & cur)
void copySelectionToStack(LCursor & cur, CutStack & cutstack)
{
// this doesn't make sense, if there is no selection
if (!cur.selection())
@ -611,7 +606,7 @@ void copySelectionToStack(LCursor & cur)
++pos;
copySelectionHelper(cur.buffer(), pars, par, cur.selEnd().pit(),
pos, cur.selEnd().pos(), cur.buffer().params().textclass);
pos, cur.selEnd().pos(), cur.buffer().params().textclass, cutstack);
}
if (cur.inMathed()) {
@ -622,10 +617,50 @@ void copySelectionToStack(LCursor & cur)
par.layout(bp.getLyXTextClass().defaultLayout());
par.insert(0, grabSelection(cur), LyXFont(), Change(Change::UNCHANGED));
pars.push_back(par);
theCuts.push(make_pair(pars, bp.textclass));
cutstack.push(make_pair(pars, bp.textclass));
}
// tell tabular that a recent copy happened
dirtyTabularStack(false);
}
}
void copySelectionToStack()
{
if (!selectionBuffer.empty())
theCuts.push(selectionBuffer[0]);
}
void copySelection(LCursor & cur, docstring const & plaintext)
{
copySelectionToStack(cur, theCuts);
// stuff the selection onto the X clipboard, from an explicit copy request
putClipboard(theCuts[0].first, theCuts[0].second, plaintext);
}
void saveSelection(LCursor & cur)
{
lyxerr[Debug::ACTION] << "cap::saveSelection: `"
<< to_utf8(cur.selectionAsString(true)) << "'." << endl;
if (cur.selection())
copySelectionToStack(cur, selectionBuffer);
// tell X whether we now have a valid selection
theSelection().haveSelection(cur.selection());
}
bool selection()
{
return !selectionBuffer.empty();
}
void clearSelection()
{
selectionBuffer.clear();
}
@ -660,11 +695,25 @@ void pasteParagraphList(LCursor & cur, ParagraphList const & parlist,
}
void pasteFromStack(LCursor & cur, ErrorList & errorList, size_t sel_index)
{
// this does not make sense, if there is nothing to paste
if (!checkPastePossible(sel_index))
return;
recordUndo(cur);
pasteParagraphList(cur, theCuts[sel_index].first,
theCuts[sel_index].second, errorList);
cur.setSelection();
saveSelection(cur);
}
void pasteClipboard(LCursor & cur, ErrorList & errorList, bool asParagraphs)
{
// Use internal clipboard if it is the most recent one
if (theClipboard().isInternal()) {
pasteSelection(cur, errorList, 0);
pasteClipboard(cur, errorList, 0);
return;
}
@ -696,15 +745,13 @@ void pasteClipboard(LCursor & cur, ErrorList & errorList, bool asParagraphs)
}
void pasteSelection(LCursor & cur, ErrorList & errorList, size_t sel_index)
void pasteSelection(LCursor & cur, ErrorList & errorList)
{
// this does not make sense, if there is nothing to paste
if (!checkPastePossible(sel_index))
if (selectionBuffer.empty())
return;
recordUndo(cur);
pasteParagraphList(cur, theCuts[sel_index].first,
theCuts[sel_index].second, errorList);
pasteParagraphList(cur, selectionBuffer[0].first,
selectionBuffer[0].second, errorList);
cur.setSelection();
}
@ -735,6 +782,7 @@ void replaceSelectionWithString(LCursor & cur, docstring const & str, bool backw
cur.setSelection(selbeg, -int(str.length()));
} else
cur.setSelection(selbeg, str.length());
saveSelection(cur);
}

View File

@ -68,15 +68,24 @@ void copySelection(LCursor & cur);
* clipboard
*/
void copySelection(LCursor & cur, docstring const & plaintext);
/// Push the current selection to the cut buffer.
void copySelectionToStack(LCursor & cur);
/// Push the selection buffer to the cut buffer.
void copySelectionToStack();
/// Store the current selection in the internal selection buffer
void saveSelection(LCursor & cur);
/// Is a selection available in our selection buffer?
bool selection();
/// Clear our selection buffer
void clearSelection();
/// Paste the current selection at \p cur
/// Does handle undo. Does only work in text, not mathed.
void pasteSelection(LCursor & cur, ErrorList &);
/// Replace the current selection with the clipboard contents (internal or
/// external: which is newer)
/// Does handle undo. Does only work in text, not mathed.
void pasteClipboard(LCursor & cur, ErrorList & errorList, bool asParagraphs = true);
/// Replace the current selection with cut buffer \c sel_index
/// Does handle undo. Does only work in text, not mathed.
void pasteSelection(LCursor & cur, ErrorList &, size_t sel_index = 0);
void pasteFromStack(LCursor & cur, ErrorList & errorList, size_t sel_index);
/// Paste the paragraph list \p parlist at the position given by \p cur.
/// Does not handle undo. Does only work in text, not mathed.

View File

@ -41,8 +41,6 @@
#include "mathed/InsetMathScript.h"
#include "mathed/MathMacroTable.h"
#include "frontends/Selection.h"
#include "support/limited_stack.h"
#include <boost/assert.hpp>
@ -560,7 +558,7 @@ bool LCursor::selHandle(bool sel)
resetAnchor();
selection() = sel;
theSelection().haveSelection(sel);
cap::saveSelection(*this);
return true;
}

View File

@ -50,6 +50,7 @@ namespace lyx {
using cap::dirtyTabularStack;
using cap::tabularStackDirty;
using cap::saveSelection;
using graphics::PreviewLoader;
@ -501,12 +502,12 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
}
if (cmd.button() == mouse_button::button2) {
if (bvcur.selection()) {
if (cap::selection()) {
// See comment in LyXText::dispatch why we
// do this
// FIXME This does not use paste_tabular,
// another reason why paste_tabular should go.
cap::copySelectionToStack(bvcur);
cap::copySelectionToStack();
cmd = FuncRequest(LFUN_PASTE, "0");
} else {
cmd = FuncRequest(LFUN_PRIMARY_SELECTION_PASTE,
@ -537,7 +538,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
//lyxerr << "# InsetTabular::MouseRelease\n" << bvcur << endl;
if (cmd.button() == mouse_button::button1) {
if (bvcur.selection())
theSelection().haveSelection(true);
saveSelection(bvcur);// theSelection().haveSelection(true);
} else if (cmd.button() == mouse_button::button3)
InsetTabularMailer(*this).showDialog(&cur.bv());
break;
@ -545,11 +546,13 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
case LFUN_CELL_BACKWARD:
movePrevCell(cur);
cur.selection() = false;
saveSelection(cur);
break;
case LFUN_CELL_FORWARD:
moveNextCell(cur);
cur.selection() = false;
saveSelection(cur);
break;
case LFUN_CHAR_FORWARD_SELECT:
@ -558,7 +561,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
if (!cur.result().dispatched()) {
isRightToLeft(cur) ? movePrevCell(cur) : moveNextCell(cur);
if (cmd.action == LFUN_CHAR_FORWARD_SELECT)
theSelection().haveSelection(cur.selection());
saveSelection(cur);
if (sl == cur.top())
cmd = FuncRequest(LFUN_FINISHED_RIGHT);
else
@ -572,7 +575,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
if (!cur.result().dispatched()) {
isRightToLeft(cur) ? moveNextCell(cur) : movePrevCell(cur);
if (cmd.action == LFUN_CHAR_BACKWARD_SELECT)
theSelection().haveSelection(cur.selection());
saveSelection(cur);
if (sl == cur.top())
cmd = FuncRequest(LFUN_FINISHED_LEFT);
else
@ -595,7 +598,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
cur.bv().textMetrics(cell(cur.idx())->getText(0));
cur.pos() = tm.x2pos(cur.pit(), 0, cur.targetX());
if (cmd.action == LFUN_DOWN_SELECT)
theSelection().haveSelection(cur.selection());
saveSelection(cur);
}
if (sl == cur.top()) {
// we trick it to go to the RIGHT after leaving the
@ -622,7 +625,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
tm.parMetrics(cur.lastpit());
cur.pos() = tm.x2pos(cur.pit(), pm.rows().size()-1, cur.targetX());
if (cmd.action == LFUN_UP_SELECT)
theSelection().haveSelection(cur.selection());
saveSelection(cur);
}
if (sl == cur.top()) {
cmd = FuncRequest(LFUN_FINISHED_UP);
@ -746,7 +749,7 @@ void InsetTabular::doDispatch(LCursor & cur, FuncRequest & cmd)
case LFUN_PASTE:
if (tabularStackDirty() && theClipboard().isInternal()) {
recordUndoInset(cur, Undo::INSERT);
pasteSelection(cur);
pasteClipboard(cur);
break;
}
cell(cur.idx())->dispatch(cur, cmd);
@ -1823,7 +1826,7 @@ bool InsetTabular::copySelection(LCursor & cur)
}
bool InsetTabular::pasteSelection(LCursor & cur)
bool InsetTabular::pasteClipboard(LCursor & cur)
{
if (!paste_tabular)
return false;

View File

@ -179,7 +179,7 @@ private:
///
bool copySelection(LCursor & cur);
///
bool pasteSelection(LCursor & cur);
bool pasteClipboard(LCursor & cur);
///
void cutSelection(LCursor & cur);
///

View File

@ -29,7 +29,6 @@
#include "undo.h"
#include "frontends/Alert.h"
#include "frontends/Selection.h"
#include "support/convert.h"
#include "support/docstream.h"
@ -366,7 +365,6 @@ bool findNextChange(BufferView * bv)
// Now put cursor to end of selection:
bv->cursor().setCursor(cur);
bv->cursor().setSelection();
theSelection().haveSelection(bv->cursor().selection());
return true;
}

View File

@ -50,7 +50,6 @@ using std::istream;
using std::istringstream;
using std::vector;
class GridInsetMailer : public MailInset {
public:
GridInsetMailer(InsetMathGrid & inset) : inset_(inset) {}

View File

@ -1400,7 +1400,6 @@ bool InsetMathHull::searchForward(BufferView * bv, string const & str,
MathArray const & a = top.asInsetMath()->cell(top.idx_);
if (a.matchpart(ar, top.pos_)) {
bv->cursor().setSelection(it, ar.size());
theSelection().haveSelection(bv->cursor().selection());
current = it;
top.pos_ += ar.size();
bv->update();

View File

@ -580,7 +580,7 @@ void InsetMathNest::doDispatch(LCursor & cur, FuncRequest & cmd)
cur.selection() = true;
cur.pos() = cur.lastpos();
cur.idx() = cur.lastidx();
theSelection().haveSelection(true);
cap::saveSelection(cur);
break;
case LFUN_PARAGRAPH_UP:
@ -1151,10 +1151,11 @@ void InsetMathNest::lfunMousePress(LCursor & cur, FuncRequest & cmd)
cur.updateFlags(Update::Decoration | Update::FitCursor);
} else if (cmd.button() == mouse_button::button2) {
MathArray ar;
if (cur.selection()) {
if (cap::selection()) {
// See comment in LyXText::dispatch why we do this
cap::copySelectionToStack(bv.cursor());
asArray(bv.cursor().selectionAsString(false), ar);
cap::copySelectionToStack();
cmd = FuncRequest(LFUN_PASTE, "0");
doDispatch(cur, cmd);
} else
asArray(theSelection().get(), ar);
@ -1185,10 +1186,13 @@ void InsetMathNest::lfunMouseRelease(LCursor & cur, FuncRequest & cmd)
//lyxerr << "## lfunMouseRelease: buttons: " << cmd.button() << endl;
if (cmd.button() == mouse_button::button1) {
if (cur.bv().cursor().selection())
theSelection().haveSelection(true);
if (!cur.selection())
cur.noUpdate();
else {
LCursor & bvcur = cur.bv().cursor();
bvcur.selection() = true;
cap::saveSelection(bvcur);
}
return;
}

View File

@ -53,7 +53,6 @@
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "frontends/Selection.h"
#include "insets/insettext.h"
#include "insets/insetbibitem.h"
@ -829,7 +828,7 @@ void LyXText::selectWord(LCursor & cur, word_location loc)
cur.resetAnchor();
setCursor(cur, to.pit(), to.pos());
cur.setSelection();
theSelection().haveSelection(cur.selection());
cap::saveSelection(cur);
}

View File

@ -76,9 +76,10 @@ namespace lyx {
using cap::copySelection;
using cap::cutSelection;
using cap::pasteFromStack;
using cap::pasteClipboard;
using cap::pasteSelection;
using cap::replaceSelection;
using cap::saveSelection;
using support::isStrUnsignedInt;
using support::token;
@ -122,7 +123,7 @@ namespace {
{
if (selecting || cur.mark())
cur.setSelection();
theSelection().haveSelection(cur.selection());
saveSelection(cur);
cur.bv().switchKeyMap();
}
@ -138,7 +139,6 @@ namespace {
{
recordUndo(cur);
docstring sel = cur.selectionAsString(false);
//lyxerr << "selection is: '" << sel << "'" << endl;
// It may happen that sel is empty but there is a selection
replaceSelection(cur);
@ -434,6 +434,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
cur.undispatched();
cmd = FuncRequest(LFUN_FINISHED_RIGHT);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_CHAR_BACKWARD:
@ -450,6 +452,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
cur.undispatched();
cmd = FuncRequest(LFUN_FINISHED_LEFT);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_UP:
@ -464,6 +468,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
cur.undispatched();
cmd = FuncRequest(LFUN_FINISHED_UP);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_DOWN:
@ -478,18 +484,24 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
cur.undispatched();
cmd = FuncRequest(LFUN_FINISHED_DOWN);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_PARAGRAPH_UP:
case LFUN_PARAGRAPH_UP_SELECT:
needsUpdate |= cur.selHandle(cmd.action == LFUN_PARAGRAPH_UP_SELECT);
needsUpdate |= cursorUpParagraph(cur);
if (cur.selection())
saveSelection(cur);
break;
case LFUN_PARAGRAPH_DOWN:
case LFUN_PARAGRAPH_DOWN_SELECT:
needsUpdate |= cur.selHandle(cmd.action == LFUN_PARAGRAPH_DOWN_SELECT);
needsUpdate |= cursorDownParagraph(cur);
if (cur.selection())
saveSelection(cur);
break;
case LFUN_SCREEN_UP:
@ -501,6 +513,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
} else {
cursorPrevious(cur);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_SCREEN_DOWN:
@ -513,6 +527,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
} else {
cursorNext(cur);
}
if (cur.selection())
saveSelection(cur);
break;
case LFUN_LINE_BEGIN:
@ -525,6 +541,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
case LFUN_LINE_END_SELECT:
needsUpdate |= cur.selHandle(cmd.action == LFUN_LINE_END_SELECT);
needsUpdate |= cursorEnd(cur);
if (cur.selection())
saveSelection(cur);
break;
case LFUN_WORD_FORWARD:
@ -534,6 +552,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
needsUpdate |= cursorLeftOneWord(cur);
else
needsUpdate |= cursorRightOneWord(cur);
if (cur.selection())
saveSelection(cur);
break;
case LFUN_WORD_BACKWARD:
@ -543,6 +563,8 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
needsUpdate |= cursorRightOneWord(cur);
else
needsUpdate |= cursorLeftOneWord(cur);
if (cur.selection())
saveSelection(cur);
break;
case LFUN_WORD_SELECT: {
@ -781,7 +803,7 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
pasteClipboard(cur, bv->buffer()->errorList("Paste"));
else {
string const arg(to_utf8(cmd.argument()));
pasteSelection(cur, bv->buffer()->errorList("Paste"),
pasteFromStack(cur, bv->buffer()->errorList("Paste"),
isStrUnsignedInt(arg) ?
convert<unsigned int>(arg) :
0);
@ -960,7 +982,7 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
cursorEnd(cur);
cur.setSelection();
bv->cursor() = cur;
theSelection().haveSelection(cur.selection());
saveSelection(cur);
}
break;
@ -968,7 +990,6 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
if (cmd.button() == mouse_button::button1) {
selectWord(cur, WHOLE_WORD_STRICT);
bv->cursor() = cur;
theSelection().haveSelection(cur.selection());
}
break;
@ -983,15 +1004,11 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
// it could get cleared on the unlocking of the inset so
// we have to check this first
bool paste_internally = false;
if (cmd.button() == mouse_button::button2 && cur.selection()) {
// Copy the selection to the clipboard stack. This
// is done for two reasons:
// - We want it to appear in the "Edit->Paste recent"
// menu.
// - We can then use the normal copy/paste machinery
// instead of theSelection().get() to preserve
// formatting of the pasted stuff.
cap::copySelectionToStack(cur.bv().cursor());
if (cmd.button() == mouse_button::button2 && cap::selection()) {
// Copy the selection buffer to the clipboard
// stack, because we want it to appear in the
// "Edit->Paste recent" menu.
cap::copySelectionToStack();
paste_internally = true;
}
@ -1002,9 +1019,13 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
// if there is a local selection in the current buffer,
// insert this
if (cmd.button() == mouse_button::button2) {
if (paste_internally)
lyx::dispatch(FuncRequest(LFUN_PASTE, "0"));
else
if (paste_internally) {
cap::pasteSelection(cur, bv->buffer()->errorList("Paste"));
bv->buffer()->errors("Paste");
cur.clearSelection(); // bug 393
bv->switchKeyMap();
finishUndo();
} else
lyx::dispatch(FuncRequest(LFUN_PRIMARY_SELECTION_PASTE, "paragraph"));
}
@ -1061,10 +1082,18 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
if (cmd.button() == mouse_button::button2)
break;
// finish selection
if (cmd.button() == mouse_button::button1) {
if (cur.selection())
theSelection().haveSelection(true);
// if there is new selection, update persistent
// selection, otherwise, single click does not
// clear persistent selection buffer
if (cur.selection()) {
// finish selection
// if double click, cur is moved to the end of word by selectWord
// but bvcur is current mouse position
LCursor & bvcur = cur.bv().cursor();
bvcur.selection() = true;
saveSelection(bvcur);
}
needsUpdate = false;
cur.noUpdate();
}
@ -1083,13 +1112,9 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
// "auto_region_delete", which defaults to
// true (on).
if (lyxrc.auto_region_delete) {
if (lyxrc.auto_region_delete)
if (cur.selection())
cutSelection(cur, false, false);
// cutSelection clears the X selection.
else
theSelection().haveSelection(false);
}
cur.clearSelection();
LyXFont const old_font = real_current_font;
@ -1508,6 +1533,7 @@ void LyXText::dispatch(LCursor & cur, FuncRequest & cmd)
case LFUN_ESCAPE:
if (cur.selection()) {
cur.selection() = false;
saveSelection(cur);
} else {
cur.undispatched();
cmd = FuncRequest(LFUN_FINISHED_RIGHT);