Background. "Bidi" refers to the bidirectional text, that is a text where traditional left-to-right scripts are intermixed with right-to-left ones, such as Arabic, Hebrew, Farsi, Thaana, etc. Such text may look like this: "Hello އުސެރް އެކްސްޕެރިއްނެސެ". Since most readers of this board do not know these languages, I will use the same convention as the Unicode documents: in example strings the lowercase letters will denote an ltr language, and uppercase letters will be an rtl script. Thus, the example above is "hello RESU ECNEIREPXE" (note that I manually switched words order because this is how word-wrap in rtl text is supposed to work).
The Question. I'll put it here explicitly, or otherwise it might get lost behind all the minute details of the subsequent exposition. So: What considerations should be taken into account when making a bidi-aware text editor? How should those elements function around the bidi text?
Next I will point out the issues I'm aware of, and their possible solutions. I'm seeking an expert opinion on which problems I overlooked, and whether there are better solutions than I came up with. Also it might be worth mentioning that I'm working on an IDE, that is a text editor for programming languages. Thus, the bulk of the text will always be ltr, and only small fragments (string literals and comments) right-to-left.
Also, there are very detailed documents from Unicode workgroup on how the bidi text should be stored, displayed, rendered, and searched. This is not an attempt to trespass on the territory of Unicode's authority. Rather, I'd like to understand how should the interaction between the user and the bidi text be organized – something which Unicode standard omits.
Text cursor. The function of the text cursor is to indicate the place where the next character you type will appear. The cursor guides the eye: as an exercise try writing a reply to this post by looking at the preview window only, where there is no cursor. The unfortunate feature of the bidi text is that it is not always known where the next character will appear. For example, if you type a logical string abcABC
, it will appear on your screen as "abc|CBA
". If at this point you type D
, it will appear where the cursor is, just as we need. But if you continue with an ltr letter d
, it should become appended to the end, like this: abcCBAd|
. This is the correct placement by the Unicode standard, and the only problem is that the letter appeared not where the cursor promised it should have had.
The potential solution is to display two cursors in such case: abc|CBA|
. The second one will probably be less prominent, since it is less likely that the next typed letter will go there. It would actually be an interesting exercise to make the brightness of the second cursor based on an estimated probability of the next character appearing there.
Some editors show a different type of cursor inside the rtl text: a vertical line with a small triangle pointing left. This indicates that the cursor will move left after you type something. However I'm not exactly sure if this is useful, since an rtl user will already expect that the cursor will move in that direction. There is no need to indicate that your software will behave in the wave you expect it to.
Text cursor, movement. Suppose you typed abcDCBAd|
, what should happen as you press the ← key? It seems to me that the most logical behavior is to move the caret leftwards, one character at a time: abcDCBA|d
, abcDCB|Ad
, abcDC|BAd
, etc. However in many editors the behavior is different: they move the cursor backwards, which within rtl text means to the right: abcDCBAd|
, abc|DCBAd
, abcD|CBAd
, abcDC|BAd
, abcDCB|Ad
, abc|DCBAd
, ab|cDCBAd
, etc (yes, the cursor never visits the position between A
and d
). I don't know if this is a bug or deliberate decision, but I don't like it. Could an actual rtl speaker provide an opinion on which behavior is more natural to them?
Text cursor, explicit. Let's say you entered abcDCBAdef|
, and then you click your mouse on the position between A
and d
. You probably want the cursor to go there: abcDCBA|def
(you'd be surprised, but in many editors it goes elsewhere!) Now you type something – where should your text go? Probably exactly in the same place where you clicked your cursor, that is between A
and d
? Haven't seen a single text editor which gets this right... The problem is that there in the underlying text ("abcABCDdef") there is no position between letters A
and d
– such position is a visual artifact. But the user doesn't care about the problems with underlying representation. He wants either to append something to the beginning of def
, or to the start of DCBA
(which is on the right). The editor should place the character at such position in the text, that when rendered that character appears exactly at the same position where the user explicitly indicated.
Home and End keys. As the names indicate, they should move the cursor to the beginning / end of the line respectively. In the rtl context, the beginning of the line is on the right edge, and the end is on the left. However what if your line is long and word-wrapped? Usually in such case the Home / End keys go to the start/end of the visual sub-line, but what do you do if the line has lrt/rtl text intermixed? Is the direction of movement determined by the main ltr/rtl directionality of the entire line? Or is it based on the first character of the visual subline? Or on the type of text your cursor is currently at? Or is it governed by directionality of the entire (logical) line with the exception when the entire subline has different direction? I cannot decide on this...
Del and Backspace keys. Traditionally Del deletes the next character after the cursor, and Backspace deletes the previous character. In the rtl context the "next" means on the left, and "previous" is on the right – reverse from what an ltr person expects. But again, complications arise. What's a character (note that this applies to European ltr languages as well)? For example, ޝަ
is a single character consisting of two Unicode codepoints: "Sh" ޝ
and "a" ަ
. Should they be deleted as one character, with a single Backspace keypress, or separately? On one hand we want to match the user's expectation of what a character is, and avoid creation of broken-off character pieces; on the other hand if the user made a typo and instead of "a" ަ
pressed "e" ެ
, he probably wouldn't want to retype the first character.
My idea is to make the behavior of Backspace depend on whether we have the "running" cursor or "explicit" (see discussion above). In the former case the user has just typed the wrong sub-character, so he wants to delete just it. In the letter case the character was already sitting there, it probably makes no sense to split it apart. The Del key should always delete the next character entirely.
Weak characters. As I mentioned, there are ltr characters, and rtl characters. But there are also so-called "weak" characters, whose directionality depends on the context. For example the space character. Unicode has very specific and detailed algorithm describing how to assign the directionality to such characters. The problem is that as you enter the text, the context changes and the application of those rules also changes! Here's an example: you have abc|CBA|
, and you press a space bar. Where should the space go? Depends on what you type after. If you type D
, then the text will become abc|D CBA|
, but if you type d
, the text will be abcCBA d|
. So what should we display after the space bar? (There is a rule in Unicode standard that the logical string "abcABC " should be rendered as "abcCBA ", but using this rule causes jumpy behavior in >50% of cases).
I'm thinking that a possible solution to this dilemma is to display both: abc| CBA |
. This way the natural flow of user's sight is not interrupted: as you type the text your eyes are focused on the "correct" cursor, and you see the text appearing where it is "supposed to". As you keep typing, the phantom text on the opposite site disappears, but the overall experience is that nothing jumps around. Of course, special treatment has to be given to the situation when you stop typing with both spaces still present. One of them will have to be discarded, and it will be the program's judgement call as to which one.
Ltr inside rtl. Suppose you have typed abc DCCCCBAAAA def
, and now you want to insert x
in the middle of the rtl string. You probably expect that it will look like this: abc DCCCCxBAAAA def
, but in all editors that I tried it actually becomes abc BAAAAxDCCCC def
. I think this should be considered a bug, which will be the more annoying the longer the original rtl text fragment was. The reason this bug exists is because this is how Unicode standard requires to render the logical string "abc AAAABxCCCCD def". The standard also mentions the existence of explicit overrides (embedding marks), so that "abc AAAAB{x}CCCCD def" should be rendered as expected: abc DCCCCxBAAAA def
. Unfortunately the standard doesn't place any recommendations as to when the embedding marks has to be used, but I believe that here we have exactly such a case. What do you think?
Selection. Selections are tricky: in a text like this abc ED 1|234 CBA def
if you select everything from the cursor to the start of the string, the following parts will be highlighted: [abc ]ED [1]234[ CBA] def
. This is because the underlying logical string is "abc ABC 1234 DE def", and you want to select according to the logical text or otherwise your fragment will make no sense when copied to a new location. However discontinuous and jumpy selection even if correct from technical perspective, presents very poor user experience (which is what we care about on this site).
One way to fix this would be to forbid the user from selecting parts of mixed-directionality fragments. That is, you can select freely within the ltr piece, or within the rtl-only piece. But when your selection originates at ltr position and then you hit the rtl fragment, it gets immediately selected as a whole: [abc ED 1234 CBA] def
. This is probably a desired behavior for the majority of use cases, and the rest can just delete the extraneous parts manually. What do you think?
Strings and comments. Since I'm writing an IDE, there are some programming-languages specific issues too. Here's how a program with rtl comments should be rendered:
!DETNEMUCOD LLEW YLBANOSAER MA I TSAEL TA TUB ,MARGORP YCNAF YLBIRRET A TON M'I \\
int main() { return 0; }
I think the comment sigil should be inverted, which is especially important for the block comments like /* abc */
=> \* CBA *\
. Finally, the quotation marks surrounding an rtl string should also acquire the rtl property, so that the word-wrap would work properly:
std::string description_string = TI .ENIL ENO NI TIF TONNAC DNA GNOL OOT SI GNIRTS SIHT"
".ENIL DNOCES EHT OTNO DEPPARW YLLACITAMOTUA STEG ;
Looks bizarre, but it's better than quotation marks randomly appearing in the middle of the string...
Ok, I think that's all. Did I miss anything?
Note: there was a previous question with the same topic on UX, however it was not detailed enough for my needs.