Hi!
This issue seems to have generated a lot of discussion, and I think we
sort of lost our focus. So I'm going to try to respond to the various
issues raised, and then I'll sketch out a plan for moving ahead with this.
*) Andre asked about how other programs deal with this:
I took a look at how MS-Word (2007?) and OpenOffice (1.1.2) deal with
this issue. Both of these applications provide an option of using either
logical or visual movement.
In logical mode, the arrow keys' effect is determined by the paragraph's
language: in an RTL paragraph, LEFT is logical forwards and RIGHT is
backwards, in an LTR paragraph it's the other way around. This extends
to text of the opposite direction which is embedded in the paragraph. So
even if the all the text in the paragraph is LTR, but the paragraph's
direction is RTL, the behavior will be according to the paragraph's
direction, and so the arrow keys will appear to "behave backwards" for
most of the text in that paragraph. (Note that neither of these
applications offers an equivalent of LyX's insets, due to their WYSIWYG
nature. In other words, when inserting a footnote, the footnote appears
as a footnote at the foot of the page, and as such is a totally
standalone paragraph. So getting guidance from these applications with
regard to deeply nested insets is not possible.)
In both applications, the direction is determined on a per-paragraph
level (and not per-document).
In both applications, the cursor sometimes gets stuck between paragraphs.
Visual mode in both applications means that LEFT moves left, and RIGHT
moves right, regardless of the language, and the movement is visual and
not logical (so that the cursor may actually be jumping back and forth
in the logical buffer) The backspace key still deletes logical
"backwards", so in LTR it deletes to the LEFT, but in RTL it deletes to
the right.
In OO, also in visual mode, the cursor gets stuck between RTL and LTR
paragraphs. In Word it doesn't get stuck between paragraphs in visual mode.
*) Martin asked: "don't you think that cursor movement depending only on
the local language (i.e., the deepest inset on the cursor stack) is
easier and more transparent to implement?"
Well yes, but only trivially so. In terms of ease of implementation, all
I did was add a "bottom()" method in there --- that's hardly very
opaque... In terms of transparency --- well, it is now also
well-commented, so I don't think that this should be grounds for
rejecting this approach.
*) Abdel said: "I personally tend to think that the only clear option,
one that does not leave room for complicated interpretation is to go for
screen oriented movement: right is right and left is left. Of course
supporting both mode would be better."
+1. Yes, I agree that visual mode is clearer. However, it has its own
problems:
*) A selection may not be logically contiguous
*) Under this interpretation, what should the "backspace" key mean?
Delete the previous character, or delete the character to the left of
the cursor (where the arrow points)? It's not the same thing in RTL...
And which character does the "delete" key delete? Depending on your
answer to the previous question, "delete" and "backspace" may actually
delete the same character, which doesn't make too much sense. So it's
pretty clear (and that seems to be the consensus in other programs, as
well), that backspace should still delete the logically previous
character, even in visual mode.
So yes, I'm all for adding support for visual mode, I opened a new
feature request for this in bugzilla
(http://bugzilla.lyx.org/show_bug.cgi?id=3577). However, I'm against
*replacing* the current approach --- logical mode --- with visual mode.
Rather, visual mode should be added as another option, and the user
should be able to choose between them by setting a preference.
*) Andre said: "As a plain non-RTL-user I'd think the same. But of
course the vote of someone actually using it should count more."
+2. Yes, it is a rather humorous to hear all these opinions about
whether or not this or that approach makes sense, given by people who
have never written in Bidi. I repeat: it *doesn't* make sense, and it
never will, there's no perfect solution except for switching the
direction of the language (and that probably won't happen too soon ;) )
--- so please, instead of giving opinions about which approach does or
does not make sense from the user's point of view, it would be much more
helpful if you could help us out with the technical, programmatic,
issues (and of course, make sure that nothing that we do interferes with
reguar, non-bidi users). We've offered a few patches, but we need help
working out the technical details, and you guys understand those better
than we do. That's where we really need your help.
*) Finally, please keep in mind that what users have been complaining
about is not "the arrow keys move in the opposite directions", but
rather "when moving with the arrow keys, the cursor gets stuck inside
insets". And the only way to avoid that (within the logical mode) is by
being consistent about the logical interpretation of an arrow key within
a paragraph --- even if the inset is a few levels deep. As soon as you
change the logical interpretation of an arrow key --- that's where you
get stuck in logical mode. Similarly, in visual mode, the only way to
avoid getting stuck is by being consistent about the *visual*
interpretation of the arrow keys.
=================================================================
So how do we move ahead with this?
1. Since the current implementation is of logical movement, I strongly
urge that we stick with that. And in order to fix the problem with that
(of the cursor getting stuck within insets), I suggest applying Elazar's
patch for math insets (attached math_insets.diff), and my patch for
other insets and for movement around insets (also attached
rtl_insets.diff; modified from my previous version, in accordance with
JMarc's comments; also, I consolidated a bit, so that policy changes ---
e.g., inner or outer paragraph --- need only touch one place, and not
five or six).
If you have any technical comments about these patches, let's work them
out. Otherwise, let's please commit them?
2. It would be great if we could also add support for visual mode (see
bug http://bugzilla.lyx.org/show_bug.cgi?id=3577), as a preference.
Elazar has sent a patch which starts moving in that direction, and I
have responded to it (see thread
http://thread.gmane.org/gmane.editors.lyx.devel/82838). We need help
with it, though, so please do help us if you can.
Just in order to clarify future discussions of this, let's try to be
clear about whether any given discussion is about "logical mode" or
"visual mode" --- both of which we would like to support.
Thanks!
Dov
Index: src/mathed/InsetMathNest.cpp
===================================================================
--- src/mathed/InsetMathNest.cpp (revision 18237)
+++ src/mathed/InsetMathNest.cpp (working copy)
@@ -492,6 +492,10 @@
cur.autocorrect() = false;
cur.clearTargetX();
cur.macroModeClose();
+ if (cur.isRTL() )
+ goto goto_char_backwards;
+
+ goto_char_forwards:
if (cur.pos() != cur.lastpos() && cur.openable(cur.nextAtom()))
{
cur.pushLeft(*cur.nextAtom().nucleus());
cur.inset().idxFirst(cur);
@@ -511,6 +515,10 @@
cur.autocorrect() = false;
cur.clearTargetX();
cur.macroModeClose();
+ if (cur.isRTL())
+ goto goto_char_forwards;
+
+ goto_char_backwards:
if (cur.pos() != 0 && cur.openable(cur.prevAtom())) {
cur.posLeft();
cur.push(*cur.nextAtom().nucleus());
Index: src/Text.h
===================================================================
--- src/Text.h (revision 18237)
+++ src/Text.h (working copy)
@@ -328,6 +328,8 @@
docstring getPossibleLabel(Cursor & cur) const;
/// is this paragraph right-to-left?
bool isRTL(Buffer const &, Paragraph const & par) const;
+ /// should cursor movements be reversed (for bidi)?
+ bool reverseDirections(Cursor const & cur) const;
///
bool checkAndActivateInset(Cursor & cur, bool front);
Index: src/Text3.cpp
===================================================================
--- src/Text3.cpp (revision 18237)
+++ src/Text3.cpp (working copy)
@@ -298,6 +298,19 @@
}
+bool Text::reverseDirections(Cursor const & cur) const
+{
+ /*
+ * We determine the directions based on the direction of the
+ * bottom() --- i.e., outermost --- paragraph, because that is
+ * the only way to achieve consistency of the arrow's movements
+ * within a paragraph, and thus avoid situations in which the
+ * cursor gets stuck.
+ */
+ return isRTL(*cur.bv().buffer(), cur.bottom().paragraph());
+}
+
+
void Text::dispatch(Cursor & cur, FuncRequest & cmd)
{
LYXERR(Debug::ACTION) << "Text::dispatch: cmd: " << cmd << endl;
@@ -433,7 +446,7 @@
//lyxerr << BOOST_CURRENT_FUNCTION
// << " LFUN_CHAR_FORWARD[SEL]:\n" << cur << endl;
needsUpdate |= cur.selHandle(cmd.action ==
LFUN_CHAR_FORWARD_SELECT);
- if (isRTL(*cur.bv().buffer(), cur.paragraph()))
+ if (reverseDirections(cur))
needsUpdate |= cursorLeft(cur);
else
needsUpdate |= cursorRight(cur);
@@ -451,7 +464,7 @@
case LFUN_CHAR_BACKWARD_SELECT:
//lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur <<
endl;
needsUpdate |= cur.selHandle(cmd.action ==
LFUN_CHAR_BACKWARD_SELECT);
- if (isRTL(*cur.bv().buffer(), cur.paragraph()))
+ if (reverseDirections(cur))
needsUpdate |= cursorRight(cur);
else
needsUpdate |= cursorLeft(cur);
@@ -557,7 +570,7 @@
case LFUN_WORD_FORWARD:
case LFUN_WORD_FORWARD_SELECT:
needsUpdate |= cur.selHandle(cmd.action ==
LFUN_WORD_FORWARD_SELECT);
- if (isRTL(*cur.bv().buffer(), cur.paragraph()))
+ if (reverseDirections(cur))
needsUpdate |= cursorLeftOneWord(cur);
else
needsUpdate |= cursorRightOneWord(cur);
@@ -568,7 +581,7 @@
case LFUN_WORD_BACKWARD:
case LFUN_WORD_BACKWARD_SELECT:
needsUpdate |= cur.selHandle(cmd.action ==
LFUN_WORD_BACKWARD_SELECT);
- if (isRTL(*cur.bv().buffer(), cur.paragraph()))
+ if (reverseDirections(cur))
needsUpdate |= cursorRightOneWord(cur);
else
needsUpdate |= cursorLeftOneWord(cur);
@@ -1433,11 +1446,16 @@
case LFUN_FINISHED_LEFT:
LYXERR(Debug::DEBUG) << "handle LFUN_FINISHED_LEFT:\n" << cur
<< endl;
+ if (reverseDirections(cur)) {
+ ++cur.pos();
+ }
break;
case LFUN_FINISHED_RIGHT:
LYXERR(Debug::DEBUG) << "handle LFUN_FINISHED_RIGHT:\n" << cur
<< endl;
- ++cur.pos();
+ if (!reverseDirections(cur)) {
+ ++cur.pos();
+ }
break;
case LFUN_FINISHED_UP: