[vte/wip/egmont/bidi: 4/23] BiDi work up to Feb 2019 squashed
- From: Egmont Koblinger <egmontkob src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte/wip/egmont/bidi: 4/23] BiDi work up to Feb 2019 squashed
- Date: Fri, 31 May 2019 13:03:43 +0000 (UTC)
commit ddb842e2dc62a3bc31c9c6ae41f61b0457ba5467
Author: Egmont Koblinger <egmont gmail com>
Date: Fri Aug 17 18:43:19 2018 +0200
BiDi work up to Feb 2019 squashed
BIDI-STATUS | 64 +++++
doc/bidi.txt | 205 ++++++++++++++
meson.build | 9 +
meson_options.txt | 7 +
src/bidi.cc | 724 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/bidi.hh | 124 +++++++++
src/debug.cc | 1 +
src/debug.h | 1 +
src/meson.build | 3 +
src/modes-ecma.hh | 17 +-
src/modes-private.hh | 26 ++
src/modes.hh | 4 +-
src/parser-cmd.hh | 4 +-
src/parser-csi.hh | 4 +-
src/ring.cc | 20 +-
src/ring.hh | 10 +-
src/vte.cc | 369 ++++++++++++++++++++++---
src/vte/vteterminal.h | 2 +-
src/vtedefines.hh | 3 +
src/vtedraw.cc | 7 +
src/vtedraw.hh | 2 +
src/vtegtk.cc | 6 +
src/vteinternal.hh | 26 +-
src/vterowdata.cc | 3 +
src/vterowdata.hh | 3 +-
src/vteseq.cc | 71 ++++-
src/vteunistr.cc | 33 +++
src/vteunistr.h | 23 ++
28 files changed, 1707 insertions(+), 64 deletions(-)
---
diff --git a/BIDI-STATUS b/BIDI-STATUS
new file mode 100644
index 00000000..1a29c4f4
--- /dev/null
+++ b/BIDI-STATUS
@@ -0,0 +1,64 @@
+Done:
+- Implicit (level 1) and Explicit (a small subset only) modes (SM / RM 8).
+- Overall LTR or RTL direction (SPD 0 / 3 or SCP 1 / 2).
+- Possibility to autodetect paragraph direction (DECSET / DECRST 2501).
+- Possibility to make box drawing characters mirrorable (DECSET / DECRST 2500).
+- I-Beam cursor shows the character's resolved directionality when the
+ paragraph has a foreign directionality character.
+- Mouse highlighting, text copying (logical in normal modes, visual in
+ rectangle mode).
+- Mouse reporting.
+- Regex match and explicit hyperlink underlining on hover.
+- VTE_DEBUG=bidi highlights characters with resolved RTL directionality.
+- Arabic shaping using Unicode presentation forms.
+- Test file.
+- Configure flag.
+- Keyboard arrow swapping.
+
+Bugs:
+- The way the modes apply to paragraphs, and what happens when a paragraph
+ is split or two paragraphs are joined is just a first hack, needs to be
+ reviewed, adjusted, fixed properly.
+- SPD should also update all the onscreen lines.
+
+Missing from first release:
+- The entire screen is always invalidated. Have some more fine grained
+ solution (see also vte #26).
+- Probably we'd need to make Erase in Display (Below or All) sequences turn
+ the previous line to hard wrapped, requiring to keep one more line in the
+ ring's writable area.
+- Design doc review & publishing, public tracking issue.
+- Brief summary about the design and implemented features in the source tree.
+- Code cleanup and review, of course.
+
+Planned future improvements:
+- Real shaping (harfbuzz?).
+- Right-align RTL glyphs.
+- Implicit mode level 2 (handling BiDi control characters).
+- Mirror the glyphs that don't have mirrored counterpart.
+- Play with other possibilities for placing the cursor (especially when it's
+ at the end of the logical line).
+- Play with better placement of the preedit box.
+- Remember some lines that are no longer user-accessible as they scroll out,
+ to properly BiDi the still remaining part of that paragraph.
+- Possibility for default RTL directionality?
+- API (for what exactly)?
+
+Not planned at all:
+- Operating on the presentation component (DCSM reset).
+- BiDi in explicit mode by transferring the embedding levels (SDS, SRS...).
+- Escape sequences that modify the emulation behavior (SIMD...).
+
+Useful aliases:
+ alias ltr='echo -ne "\e[1 k"'
+ alias rtl='echo -ne "\e[2 k"'
+ alias implicit='echo -ne "\e[8h"'
+ alias explicit='echo -ne "\e[8l"'
+ alias bidi='echo -ne "\e[8h"' # same as implicit
+ alias nobidi='echo -ne "\e[8l"' # same as explicit
+ alias box-mirror='echo -ne "\e[?2500h"'
+ alias box-normal='echo -ne "\e[?2500l"'
+ alias auto='echo -ne "\e[?2501h"'
+ alias noauto='echo -ne "\e[?2501l"'
+ alias kbdswap='echo -ne "\e[?1243h"'
+ alias nokbdswap='echo -ne "\e[?1243l"'
diff --git a/doc/bidi.txt b/doc/bidi.txt
new file mode 100644
index 00000000..a8145824
--- /dev/null
+++ b/doc/bidi.txt
@@ -0,0 +1,205 @@
+ ╔════════════════════════════╗
+═════════════════════════╣ BiDi test – for 80 columns ╠═════════════════════════
+ ╚════════════════════════════╝
+
+[01mIn the text examples, the subsection title sometimes shows the wire order,
+transcribed to English (“Shalom” abbreviated to occupy the same width).[m
+[32mThis is followed by the reference rendering, using similar LTR glyphs.[m
+[36mFinally the actual rendering which should match the line above.[m
+
+All words, except for subsection titles under explicit modes, should show up
+in human readable order.
+
+At box tests the reference rendering is upside down, so you should get nice
+squares everywhere.
+
+ ┌──────────────┐
+────────────────────────────────┤ Implicit LTR ├────────────────────────────────
+ └──────────────┘
+[01m⸤Hello⸣ ⸤Shlm⸣[m
+[32m⸤Hello⸣ ⸤oi7w⸣[m
+[36m⸤Hello⸣ ⸤שָׁלוֹם⸣[m
+
+[01mHello01 ⸤Hello02⸣ Hello03 Shlm01 ⸤Shlm02⸣ Shlm03[m
+[32mHello01 ⸤Hello02⸣ Hello03 03oi7w ⸢02oi7w⸥ 01oi7w[m
+[36mHello01 ⸤Hello02⸣ Hello03 שָׁלוֹם01 ⸤שָׁלוֹם02⸣ שָׁלוֹם03[m
+
+[01m⸤Shlm⸣ ⸤Hello⸣[m
+[32m⸤oi7w⸣ ⸤Hello⸣[m
+[36m⸤שָׁלוֹם⸣ ⸤Hello⸣[m
+
+[01mShlm01 ⸤Shlm02⸣ Shlm03 Hello01 ⸤Hello02⸣ Hello03[m
+[32m03oi7w ⸢02oi7w⸥ 01oi7w Hello01 ⸤Hello02⸣ Hello03[m
+[36mשָׁלוֹם01 ⸤שָׁלוֹם02⸣ שָׁלוֹם03 Hello01 ⸤Hello02⸣ Hello03[m
+
+[01mParagraph wrapping at foreign word[m
+[32mLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor w[m
+[32moi7 incididunt ut labore et dolore magna aliqua.[m
+[36mLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor שָׁלוֹם incididunt ut
labore et dolore magna aliqua.[m
+
+[01mDouble wide[m
+[32ma<z n>x[m
+[36ma<z א<ת[m
+
+[01mBox[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m
+[36m┗━┛ a╚═╝z א┘─└ת ╰─╯[m
+
+[01mBox in mirrored mode[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m[?2500h
+[36m┗━┛ a╚═╝z א└─┘ת ╰─╯[m[?2500l
+
+ ┌──────────────┐
+────────────────────────────────┤ Implicit RTL ├────────────────────────────────
+ └──────────────┘
+[01m⸤Hello⸣ ⸤Shlm⸣[m
+ [32m⸢oi7w⸥ ⸢Hello⸥[m
+[36m[2 k⸤Hello⸣ ⸤שָׁלוֹם⸣[1 k[m
+
+[01mHello01 ⸤Hello02⸣ Hello03 Shlm01 ⸤Shlm02⸣ Shlm03[m
+ [32m03oi7w ⸢02oi7w⸥ 01oi7w Hello01 ⸤Hello02⸣ Hello03[m
+[36m[2 kHello01 ⸤Hello02⸣ Hello03 שָׁלוֹם01 ⸤שָׁלוֹם02⸣ שָׁלוֹם03[1 k[m
+
+[01m⸤Shlm⸣ ⸤Hello⸣[m
+ [32m⸢Hello⸥ ⸢oi7w⸥[m
+[36m[2 k⸤שָׁלוֹם⸣ ⸤Hello⸣[1 k[m
+
+[01mShlm01 ⸤Shlm02⸣ Shlm03 Hello01 ⸤Hello02⸣ Hello03[m
+ [32mHello01 ⸤Hello02⸣ Hello03 03oi7w ⸢02oi7w⸥ 01oi7w[m
+[36m[2 kשָׁלוֹם01 ⸤שָׁלוֹם02⸣ שָׁלוֹם03 Hello01 ⸤Hello02⸣ Hello03[1 k[m
+
+[01mParagraph with wrong direction (should look broken)[m
+[32mw Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor[m
+ [32m.incididunt ut labore et dolore magna aliqua oi7[m
+[36m[2 kLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor שָׁלוֹם incididunt
ut labore et dolore magna aliqua.[1 k[m
+
+[01mDouble wide[m
+ [32ma<z n>x[m
+[36m[2 kא<ת a<z[1 k[m
+
+[01mOld Hungarian Rovásírás[m
+(font at https://github.com/OldHungarian/old-hungarian-font)
+ [32mΛᛩHTΛᛩMↄH ↄ4TH4Λↄ¤ᛝ +ΛↃ[m
+[36m[2 k𐲛𐳖𐳇 𐲏𐳪𐳙𐳍𐳀𐳢𐳐𐳀𐳙 𐲢𐳛𐳮𐳁𐳤𐳑𐳢𐳁𐳤[1 k[m
+
+[01mBox[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[2 k
+[36m┛━┗ א╝═╚ת a└─┘z ╯─╰[m[1 k
+
+[01mBox in mirrored mode[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[2 k[?2500h
+[36m┗━┛ א╚═╝ת a└─┘z ╰─╯[m[?2500l[1 k
+
+ ┌──────────────────────────────┐
+────────────────────────┤ Implicit auto (LTR fallback) ├────────────────────────
+ └──────────────────────────────┘
+[01m⸤Hello⸣ ⸤Shlm⸣[m
+[32m⸤Hello⸣ ⸤oi7w⸣[m
+[36m[?2501h⸤Hello⸣ ⸤שָׁלוֹם⸣[?2501l[m
+
+[01m⸤Shlm⸣ ⸤Hello⸣[m
+ [32m⸢Hello⸥ ⸢oi7w⸥[m
+[36m[?2501h⸤שָׁלוֹם⸣ ⸤Hello⸣[?2501l[m
+
+[01mParagraph wrapping at foreign word[m
+[32mLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor w[m
+[32moi7 incididunt ut labore et dolore magna aliqua.[m
+[36m[?2501hLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor שָׁלוֹם
incididunt ut labore et dolore magna aliqua.[?2501l[m
+
+[01mBox[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m[?2501h
+[36m┗━┛ a╚═╝z א┘─└ת ╰─╯[m[?2501l
+
+[01mBox in mirrored mode[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m[?2501h[?2500h
+[36m┗━┛ a╚═╝z א└─┘ת ╰─╯[m[?2500l[?2501l
+
+ ┌──────────────────────────────┐
+────────────────────────┤ Implicit auto (RTL fallback) ├────────────────────────
+ └──────────────────────────────┘
+[01m⸤Hello⸣ ⸤Shlm⸣[m
+[32m⸤Hello⸣ ⸤oi7w⸣[m
+[36m[2 k[?2501h⸤Hello⸣ ⸤שָׁלוֹם⸣[?2501l[1 k[m
+
+[01m⸤Shlm⸣ ⸤Hello⸣[m
+ [32m⸢Hello⸥ ⸢oi7w⸥[m
+[36m[2 k[?2501h⸤שָׁלוֹם⸣ ⸤Hello⸣[?2501l[1 k[m
+
+[01mParagraph wrapping at foreign word[m
+[32mLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor w[m
+[32moi7 incididunt ut labore et dolore magna aliqua.[m
+[36m[2 k[?2501hLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor שָׁלוֹם
incididunt ut labore et dolore magna aliqua.[?2501l[1 k[m
+
+[01mBox[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[2 k[?2501h
+[36m┛━┗ א╝═╚ת a└─┘z ╯─╰[m[?2501l[1 k
+
+[01mBox in mirrored mode[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[2 k[?2501h[?2500h
+[36m┗━┛ א╚═╝ת a└─┘z ╰─╯[m[?2500l[?2501l[1 k
+
+ ┌──────────────┐
+────────────────────────────────┤ Explicit LTR ├────────────────────────────────
+ └──────────────┘
+[01m⸤Hello⸣ ⸤mlhS⸣[m
+[32m⸤Hello⸣ ⸤oi7w⸣[m
+[36m[8l⸤Hello⸣ ⸤םוֹלשָׁ⸣[8h[m
+
+[01m⸤mlhS⸣ ⸤Hello⸣[m
+[32m⸤oi7w⸣ ⸤Hello⸣[m
+[36m[8l⸤םוֹלשָׁ⸣ ⸤Hello⸣[8h[m
+
+[01mBox[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m[8l
+[36m┗━┛ a╚═╝z ת└─┘א ╰─╯[m[8h
+
+[01mBox in mirrored mode[m
+[32m┏━┓ a╔═╗z n┌─┐x ╭─╮[m[8l[?2500h
+[36m┗━┛ a╚═╝z ת└─┘א ╰─╯[m[?2500l[8h
+
+ ┌──────────────┐
+────────────────────────────────┤ Explicit RTL ├────────────────────────────────
+ └──────────────┘
+[01m⸤olleH⸣ ⸤Shlm⸣[m
+ [32m⸢oi7w⸥ ⸢Hello⸥[m
+[36m[8l[2 k⸤olleH⸣ ⸤שָׁלוֹם⸣[1 k[8h[m
+
+[01m⸤Shlm⸣ ⸤olleH⸣[m
+ [32m⸢Hello⸥ ⸢oi7w⸥[m
+[36m[8l[2 k⸤שָׁלוֹם⸣ ⸤olleH⸣[1 k[8h[m
+
+[01mFullwidth characters with underlines[m
+ [32m[4m[Lorem[24m ipsum dolor sit [4:3mamet)[m
+[36m[8l[2 k[4:3m(tema[24m tis rolod muspi [4mmeroL][1 k[8h[m
+
+[01mBox[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[8l[2 k
+[36m┛━┗ א╝═╚ת z┘─└a ╯─╰[m[1 k[8h
+
+[01mBox in mirrored mode[m
+ [32m╭─╮ a┌─┐z n╔═╗x ┏━┓[m[8l[2 k[?2500h
+[36m┗━┛ א╚═╝ת z└─┘a ╰─╯[m[?2500l[1 k[8h
+
+ ┌────────────┐
+─────────────────────────────────┤ Misc tests ├─────────────────────────────────
+ └────────────┘
+[01mAttributes – bold ("lo S"), magenta ("hl")[m
+[32mHel[1mlo[0;32m o[35mi7[32;1mw[m
+[36mHel[1mlo שָׁ[0;35mלו[36mֹם[m
+
+[01mNumbers are simply LTR – "jumps over 123 456 789 Shlm!"[m
+[32mThe quick brown fox jumps over the lazy dog The quick brown fox jumps over 123 4[m
+[32m56 789 oi7w![m
+[36mThe quick brown fox jumps over the lazy dog The quick brown fox jumps over 123 456 789 שָׁלוֹם![m
+
+[01mNumbers are inside RTL – "jumps Shlm 123 456 789 Shlm!"[m
+[32mThe quick brown fox jumps over the lazy dog The quick brown fox jumps 4 123 oi7w[m
+[32moi7w 789 56![m
+[36mThe quick brown fox jumps over the lazy dog The quick brown fox jumps שָׁלוֹם 123 456 789 שָׁלוֹם![m
+
+[01mMirroring across linebreak – "jumps Shlm <[<[<[<[ Shlm!"[m
+[32mThe quick brown fox jumps over the lazy dog The quick brown fox jumps >]>]> oi7w[m
+[32moi7w ]>]![m
+[36mThe quick brown fox jumps over the lazy dog The quick brown fox jumps שָׁלוֹם <[<[<[<[ שָׁלוֹם![m
+
+────────────────────────────────────────────────────────────────────────────────
diff --git a/meson.build b/meson.build
index 3760b3f8..451195b9 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,7 @@ project(
gtk3_req_version = '3.8.0'
gtk4_req_version = '4.0.0'
+fribidi_req_version = '1.0.0'
gio_req_version = '2.40.0'
glib_req_version = '2.40.0'
gnutls_req_version = '3.2.7'
@@ -109,6 +110,7 @@ config_h = configuration_data()
config_h.set_quoted('GETTEXT_PACKAGE', vte_gettext_domain)
config_h.set_quoted('VERSION', vte_version)
config_h.set('VTE_DEBUG', enable_debug)
+config_h.set('WITH_FRIBIDI', get_option('fribidi'))
config_h.set('WITH_GNUTLS', get_option('gnutls'))
config_h.set('WITH_ICONV', get_option('iconv'))
@@ -373,6 +375,12 @@ pcre2_dep = dependency('libpcre2-8', version: '>=' + pcre2_req_version)
pthreads_dep = dependency('threads')
zlib_dep = dependency('zlib')
+if get_option('fribidi')
+ fribidi_dep = dependency('fribidi', version: '>=' + fribidi_req_version)
+else
+ fribidi_dep = dependency('', required: false)
+endif
+
if get_option('gnutls')
gnutls_dep = dependency('gnutls', version: '>=' + gnutls_req_version)
else
@@ -443,6 +451,7 @@ output += '\n'
output += ' Coverage: ' + get_option('b_coverage').to_string() + '\n'
output += ' Debug: ' + enable_debug.to_string() + '\n'
output += ' Docs: ' + get_option('docs').to_string() + '\n'
+output += ' FRIBIDI: ' + get_option('fribidi').to_string() + '\n'
output += ' GNUTLS: ' + get_option('gnutls').to_string() + '\n'
output += ' GTK+ 3.0: ' + get_option('gtk3').to_string() + '\n'
output += ' GTK+ 4.0: ' + get_option('gtk4').to_string() + '\n'
diff --git a/meson_options.txt b/meson_options.txt
index b8a11a84..d29c66a3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -34,6 +34,13 @@ option(
description: 'Enable GObject Introspection',
)
+option(
+ 'fribidi',
+ type: 'boolean',
+ value: true,
+ description: 'Enable FriBidi support',
+)
+
option(
'gnutls',
type: 'boolean',
diff --git a/src/bidi.cc b/src/bidi.cc
new file mode 100644
index 00000000..8bdef69d
--- /dev/null
+++ b/src/bidi.cc
@@ -0,0 +1,724 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#ifdef WITH_FRIBIDI
+#include <fribidi.h>
+#endif
+
+#include "bidi.hh"
+#include "debug.h"
+#include "vtedefines.hh"
+#include "vteinternal.hh"
+
+#ifdef WITH_FRIBIDI
+static_assert (sizeof (gunichar) == sizeof (FriBidiChar), "Whoooo");
+#endif
+
+using namespace vte::base;
+
+BidiRow::BidiRow()
+{
+ m_width = 0;
+
+ /* These will be initialized / allocated on demand, when some RTL is encountered. */
+ m_width_alloc = 0;
+ m_log2vis = nullptr;
+ m_vis2log = nullptr;
+ m_vis_rtl = nullptr;
+ m_vis_shaped_char = nullptr;
+}
+
+BidiRow::~BidiRow()
+{
+ g_free (m_log2vis);
+ g_free (m_vis2log);
+ g_free (m_vis_rtl);
+ g_free (m_vis_shaped_char);
+}
+
+void BidiRow::set_width(vte::grid::column_t width)
+{
+ if (G_UNLIKELY (width > m_width_alloc)) {
+ if (m_width_alloc == 0) {
+ m_width_alloc = 128;
+ }
+ while (width > m_width_alloc) {
+ m_width_alloc *= 2;
+ }
+ m_log2vis = (vte::grid::column_t *) g_realloc (m_log2vis, sizeof (vte::grid::column_t) *
m_width_alloc);
+ m_vis2log = (vte::grid::column_t *) g_realloc (m_vis2log, sizeof (vte::grid::column_t) *
m_width_alloc);
+ m_vis_rtl = (guint8 *) g_realloc (m_vis_rtl, sizeof (guint8) * m_width_alloc);
+ m_vis_shaped_char = (gunichar *) g_realloc (m_vis_shaped_char, sizeof (gunichar) *
m_width_alloc);
+ }
+
+ m_width = width;
+}
+
+/* Converts from logical to visual column. Offscreen columns are mirrored
+ * for RTL lines, e.g. (assuming 80 columns) -1 <=> 80, -2 <=> 81 etc. */
+vte::grid::column_t BidiRow::log2vis(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_log2vis[col];
+ } else {
+ return m_base_rtl ? m_width - 1 - col : col;
+ }
+}
+
+/* Converts from visual to logical column. Offscreen columns are mirrored
+ * for RTL lines, e.g. (assuming 80 columns) -1 <=> 80, -2 <=> 81 etc. */
+vte::grid::column_t BidiRow::vis2log(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_vis2log[col];
+ } else {
+ return m_base_rtl ? m_width - 1 - col : col;
+ }
+}
+
+/* Whether the cell at the given visual position has RTL directionality.
+ * For offscreen columns the line's base direction is returned. */
+bool BidiRow::vis_is_rtl(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_vis_rtl[col];
+ } else {
+ return m_base_rtl;
+ }
+}
+
+/* Whether the cell at the given logical position has RTL directionality.
+ * For offscreen columns the line's base direction is returned. */
+bool BidiRow::log_is_rtl(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ col = m_log2vis[col];
+ return m_vis_rtl[col];
+ } else {
+ return m_base_rtl;
+ }
+}
+
+/* Get the shaped character (vteunistr) for the given visual position.
+ *
+ * The unshaped character (vteunistr) needs to be passed to this method because
+ * the BiDi component may not store it if no shaping was required, and does not
+ * store combining accents. This method takes care of preserving combining accents.
+ */
+vteunistr
+BidiRow::vis_get_shaped_char(vte::grid::column_t col, vteunistr s) const
+{
+ if (col >= m_width || m_vis_shaped_char[col] == 0)
+ return s;
+
+ return _vte_unistr_replace_base(s, m_vis_shaped_char[col]);
+}
+
+/* Whether the line's base direction is RTL. */
+bool BidiRow::base_is_rtl() const
+{
+ return m_base_rtl;
+}
+
+/* Whether the paragraph contains a foreign directionality character.
+ * This is used in the cursor, showing the character's directionality. */
+bool BidiRow::has_foreign() const
+{
+ return m_has_foreign;
+}
+
+
+RingView::RingView()
+{
+ m_ring = nullptr;
+
+ m_start = m_len = m_width = 0;
+ m_height_alloc = 32;
+
+ m_bidirows = (BidiRow **) g_malloc (sizeof (BidiRow *) * m_height_alloc);
+ for (int i = 0; i < m_height_alloc; i++) {
+ m_bidirows[i] = new BidiRow();
+ }
+
+ m_invalid = true;
+}
+
+RingView::~RingView()
+{
+ for (int i = 0; i < m_height_alloc; i++) {
+ delete m_bidirows[i];
+ }
+ g_free (m_bidirows);
+}
+
+void RingView::set_ring(Ring *ring)
+{
+ if (ring == m_ring)
+ return;
+
+ m_ring = ring;
+ m_invalid = true;
+}
+
+void RingView::set_width(vte::grid::column_t width)
+{
+ if (width == m_width)
+ return;
+
+ m_width = width;
+ m_invalid = true;
+}
+
+void RingView::set_rows(vte::grid::row_t start, vte::grid::row_t len)
+{
+ if (start == m_start && len == m_len)
+ return;
+
+ if (G_UNLIKELY (len > m_height_alloc)) {
+ int i = m_height_alloc;
+ while (len > m_height_alloc) {
+ m_height_alloc *= 2;
+ }
+ m_bidirows = (BidiRow **) g_realloc (m_bidirows, sizeof (BidiRow *) * m_height_alloc);
+ for (; i < m_height_alloc; i++) {
+ m_bidirows[i] = new BidiRow();
+ }
+ }
+
+ m_start = start;
+ m_len = len;
+ m_invalid = true;
+}
+
+void RingView::maybe_update()
+{
+ if (!m_invalid)
+ return;
+
+ vte::grid::row_t i = m_start;
+ const VteRowData *row_data = m_ring->index_safe(m_start);
+
+ if (row_data && (row_data->attr.bidi_flags & VTE_BIDI_IMPLICIT)) {
+ i = find_paragraph(m_start);
+ if (i == -1) {
+ i = explicit_paragraph(m_start, row_data->attr.bidi_flags & VTE_BIDI_RTL);
+ }
+ }
+ while (i < m_start + m_len) {
+ i = paragraph(i);
+ }
+
+ m_invalid = false;
+}
+
+BidiRow const* RingView::get_row_map(vte::grid::row_t row) const
+{
+ g_assert_cmpint (row, >=, m_start);
+ g_assert_cmpint (row, <, m_start + m_len);
+ g_assert_false (m_invalid);
+
+ return m_bidirows[row - m_start];
+}
+
+BidiRow* RingView::get_row_map_writable(vte::grid::row_t row) const
+{
+ g_assert_cmpint (row, >=, m_start);
+ g_assert_cmpint (row, <, m_start + m_len);
+
+ return m_bidirows[row - m_start];
+}
+
+/* Set up the mapping according to explicit mode for a given line. */
+void RingView::explicit_line(vte::grid::row_t row, bool rtl)
+{
+ int i;
+
+ if (G_UNLIKELY (row < m_start || row >= m_start + m_len))
+ return;
+
+ BidiRow *bidirow = get_row_map_writable(row);
+ bidirow->m_base_rtl = rtl;
+ bidirow->m_has_foreign = false;
+
+ if (G_UNLIKELY (rtl)) {
+ bidirow->set_width(m_width);
+ for (i = 0; i < m_width; i++) {
+ bidirow->m_log2vis[i] = bidirow->m_vis2log[i] = m_width - 1 - i;
+ bidirow->m_vis_rtl[i] = true;
+ bidirow->m_vis_shaped_char[i] = 0;
+ }
+ } else {
+ /* Shortcut: bidirow->m_width == 0 might denote a fully LTR line,
+ * m_width_alloc might even be 0 along with log2vis and friends being nullptr in this case.
*/
+ bidirow->set_width(0);
+ }
+}
+
+/* Set up the mapping according to explicit mode, for all the lines
+ * of a paragraph beginning at the given line.
+ * Returns the row number after the paragraph or viewport (whichever ends first). */
+vte::grid::row_t RingView::explicit_paragraph(vte::grid::row_t row, bool rtl)
+{
+ const VteRowData *row_data;
+
+ while (row < m_start + m_len) {
+ explicit_line(row, rtl);
+
+ row_data = m_ring->index_safe(row++);
+ if (row_data == nullptr || !row_data->attr.soft_wrapped)
+ break;
+ }
+ return row;
+}
+
+/* For the given row, find the first row of its paragraph.
+ * Returns -1 if have to walk backwards too much. */
+/* FIXME this could be much cheaper, we don't need to read the actual rows (text_stream),
+ * we only need the soft_wrapped flag which is stored in row_stream. Needs method in ring. */
+vte::grid::row_t RingView::find_paragraph(vte::grid::row_t row)
+{
+ vte::grid::row_t row_stop = row - VTE_BIDI_PARAGRAPH_LENGTH_MAX;
+ const VteRowData *row_data;
+
+ while (row-- > row_stop) {
+ if (row < _vte_ring_delta(m_ring))
+ return row + 1;
+ row_data = m_ring->index_safe(row);
+ if (row_data == nullptr || !row_data->attr.soft_wrapped)
+ return row + 1;
+ }
+ return -1;
+}
+
+/* Figure out the mapping for the paragraph starting at the given row.
+ * Returns the row number after the paragraph or viewport (whichever ends first). */
+vte::grid::row_t RingView::paragraph(vte::grid::row_t row)
+{
+ const VteRowData *row_data = m_ring->index_safe(row);
+ if (row_data == nullptr) {
+ return explicit_paragraph(row, false);
+ }
+
+#ifndef WITH_FRIBIDI
+ return explicit_paragraph(row, row_data->attr.bidi_flags & VTE_BIDI_RTL);
+#else
+ const VteCell *cell;
+ bool rtl;
+ bool autodir;
+ FriBidiParType pbase_dir;
+ FriBidiLevel level;
+ FriBidiChar *fribidi_chars;
+ FriBidiCharType *fribidi_chartypes;
+ FriBidiBracketType *fribidi_brackettypes;
+ FriBidiJoiningType *fribidi_joiningtypes;
+ FriBidiLevel *fribidi_levels;
+ FriBidiStrIndex *fribidi_map;
+ FriBidiStrIndex *fribidi_to_term;
+
+ BidiRow *bidirow;
+
+ if (!(row_data->attr.bidi_flags & VTE_BIDI_IMPLICIT)) {
+ return explicit_paragraph(row, row_data->attr.bidi_flags & VTE_BIDI_RTL);
+ }
+
+ rtl = row_data->attr.bidi_flags & VTE_BIDI_RTL;
+ autodir = row_data->attr.bidi_flags & VTE_BIDI_AUTO;
+
+ int lines[VTE_BIDI_PARAGRAPH_LENGTH_MAX + 1]; /* offsets to the beginning of lines */
+ lines[0] = 0;
+ int line = 0; /* line number within the paragraph */
+ int count; /* total character count */
+ int row_orig = row;
+ int tl, tv; /* terminal logical and visual */
+ int fl, fv; /* fribidi logical and visual */
+ unsigned int col;
+
+ GArray *fribidi_chars_array = g_array_new (FALSE, FALSE, sizeof (FriBidiChar));
+ GArray *fribidi_map_array = g_array_new (FALSE, FALSE, sizeof (FriBidiStrIndex));
+ GArray *fribidi_to_term_array = g_array_new (FALSE, FALSE, sizeof (FriBidiStrIndex));
+
+ /* Extract the paragraph's contents, omitting unused and fragment cells. */
+
+ /* Example of what is going on, showing the most important steps:
+ *
+ * Let's take the string produced by this command:
+ * echo -e "\u0041\u05e9\u05b8\u05c1\u05dc\u05d5\u05b9\u05dd\u0031\u0032\uff1c\u05d0"
+ *
+ * This string consists of:
+ * - English letter A
+ * - Hebrew word Shalom:
+ * - Letter Shin: ש
+ * - Combining accent Qamats
+ * - Combining accent Shin Dot
+ * - Letter Lamed: ל
+ * - Letter Vav: ו
+ * - Combining accent Holam
+ * - Letter Final Mem: ם
+ * - Digits One and Two
+ * - Full-width less-than sign U+ff1c: <
+ * - Hebrew letter Alef: א
+ *
+ * Features of this example:
+ * - Overall LTR direction for convenience (set up by the leading English letter)
+ * - Combining accents within RTL
+ * - Double width character with RTL resolved direction
+ * - A mapping that is not its own inverse (due to the digits being LTR inside RTL inside LTR),
+ * to help catch if we'd look up something in the wrong direction
+ *
+ * Not demonstrated in this example:
+ * - Wrapping a paragraph to lines
+ * - Spacing marks
+ *
+ * Pre-BiDi (logical) order, using approximating glyphs ("Shalom" is "w7io", Alef is "x"):
+ * Aw7io12<x
+ *
+ * Post-BiDi (visual) order, using approximating glyphs ("Shalom" is "oi7w", note the mirrored
less-than):
+ * Ax>12oi7w
+ *
+ * Terminal's logical cells:
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
+ * row_data: A Shin+qam+dot Lam Vav+hol Mem One Two Less Less (cont) Alef
+ *
+ * Extracted to pass to FriBidi (combining accents get -1, double wides' continuation cells are
skipped):
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_chars: A Shin qam dot Lam Vav hol Mem One Two Less Alef
+ * fribidi_map: 0 1 -1 -1 4 5 -1 7 8 9 10 11
+ * fribidi_to_term: 0 1 -1 -1 2 3 -1 4 5 6 7 9
+ *
+ * Embedding levels and other properties (shaping etc.) are looked up:
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_levels: 0 1 1 1 1 1 1 1 2 2 1 1
+ *
+ * The steps above were per-paragraph. The steps below are per-line.
+ *
+ * After fribidi_reorder_line (only this array gets shuffled):
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_map: 0 11 10 8 9 7 5 -1 4 1 -1 -1
+ *
+ * To get the visual order: walk in the new fribidi_map, and for each real entry look up the
+ * logical terminal column using fribidi_to_term:
+ * - map[0] is 0, to_term[0] is 0, hence visual column 0 belongs to logical column 0 (A)
+ * - map[1] is 11, to_term[11] is 9, hence visual column 1 belongs to logical column 9 (Alef)
+ * - map[2] is 10, to_term[10] is 7, row_data[7] is the "<" sign
+ * - this is a double wide character, we need to map the next two visual cells to two logical
cells
+ * - due to levels[10] being odd, this character has a resolved RTL direction
+ * - thus we map in reverse order: visual 2 <=> logical 8, visual 3 <=> logical 7
+ * - the glyph is also mirrorable, it'll be displayed accordingly
+ * - [3] -> 8 -> 5, so visual 4 <=> logical 5 (One)
+ * - [4] -> 9 -> 6, so visual 5 <=> logical 6 (Two)
+ * - [5] -> 7 -> 4, so visual 6 <=> logical 4 (Mem, the last, leftmost letter of Shalom)
+ * - [6] -> 5 -> 3, so visual 7 <=> logical 3 (Vav+hol)
+ * - [7] -> -1, skipped
+ * - [8] -> 4 -> 2, so visual 8 <=> logical 2 (Lam)
+ * - [9] -> 1 -> 1, so visual 9 <=> logical 1 (Shin+qam+dot, the first, rightmost letter of Shalom)
+ * - [10] -> -1, skipped
+ * - [11] -> -1, skipped
+ *
+ * Silly FriBidi API almost allows us to skip one level of indirection, by placing the to_term values
+ * in the map to be shuffled. However, we can't get the embedding levels then.
+ * TODO: File an issue for a better API.
+ */
+ while (row < _vte_ring_next(m_ring)) {
+ row_data = m_ring->index_safe(row);
+ if (row_data == nullptr)
+ break;
+
+ if (line == VTE_BIDI_PARAGRAPH_LENGTH_MAX) {
+ /* Overlong paragraph, bail out. */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return explicit_paragraph (row_orig, rtl);
+ }
+
+ /* A row_data might be longer, in case rewrapping is disabled and the window was narrowed.
+ * Truncate the logical data before applying BiDi. */
+ // FIXME what the heck to do if this truncation cuts a TAB or CJK in half???
+ for (tl = 0; tl < m_width && tl < row_data->len; tl++) {
+ auto prev_len = fribidi_chars_array->len;
+ FriBidiStrIndex val;
+
+ cell = _vte_row_data_get (row_data, tl);
+ if (cell->attr.fragment())
+ continue;
+
+ /* Extract the base character and combining accents.
+ * Convert mid-line erased cells to spaces.
+ * Note: see the static assert at the top of this file. */
+ _vte_unistr_append_to_gunichars (cell->c ? cell->c : ' ', fribidi_chars_array);
+ /* Make sure at least one character was produced. */
+ g_assert_cmpint (fribidi_chars_array->len, >, prev_len);
+
+ /* Track the base character, assign to it its current index in fribidi_chars.
+ * Don't track combining accents, assign -1's to them. */
+ val = prev_len;
+ g_array_append_val (fribidi_map_array, val);
+ val = tl;
+ g_array_append_val (fribidi_to_term_array, val);
+ prev_len++;
+ val = -1;
+ while (prev_len++ < fribidi_chars_array->len) {
+ g_array_append_val (fribidi_map_array, val);
+ g_array_append_val (fribidi_to_term_array, val);
+ }
+ }
+
+ lines[++line] = fribidi_chars_array->len;
+ row++;
+
+ if (!row_data->attr.soft_wrapped)
+ break;
+ }
+
+ if (line == 0) {
+ /* Beyond the end of the ring. */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return explicit_paragraph (row_orig, rtl);
+ }
+
+ /* Convenience stuff, we no longer need the auto-growing GArray wrapper. */
+ count = fribidi_chars_array->len;
+ fribidi_chars = (FriBidiChar *) fribidi_chars_array->data;
+ fribidi_map = (FriBidiStrIndex *) fribidi_map_array->data;
+ fribidi_to_term = (FriBidiStrIndex *) fribidi_to_term_array->data;
+
+ /* Run the BiDi algorithm on the paragraph to get the embedding levels. */
+ fribidi_chartypes = g_newa (FriBidiCharType, count);
+ fribidi_brackettypes = g_newa (FriBidiBracketType, count);
+ fribidi_joiningtypes = g_newa (FriBidiJoiningType, count);
+ fribidi_levels = g_newa (FriBidiLevel, count);
+
+ pbase_dir = autodir ? (rtl ? FRIBIDI_PAR_WRTL : FRIBIDI_PAR_WLTR)
+ : (rtl ? FRIBIDI_PAR_RTL : FRIBIDI_PAR_LTR );
+
+ fribidi_get_bidi_types (fribidi_chars, count, fribidi_chartypes);
+ fribidi_get_bracket_types (fribidi_chars, count, fribidi_chartypes, fribidi_brackettypes);
+ fribidi_get_joining_types (fribidi_chars, count, fribidi_joiningtypes);
+ level = fribidi_get_par_embedding_levels_ex (fribidi_chartypes, fribidi_brackettypes, count,
&pbase_dir, fribidi_levels);
+
+ if (level == 0) {
+ /* error */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return explicit_paragraph (row_orig, rtl);
+ }
+
+ /* Arabic shaping
+ *
+ * https://www.w3.org/TR/css-text-3/#word-break-shaping says:
+ * "When shaping scripts such as Arabic wrap [...] the characters must still be shaped (their
joining forms chosen)
+ * as if the word were still whole."
+ *
+ * Also, FriBidi's Arabic shaping methods, as opposed to fribidi_reorder_line(), don't take an
offset parameter.
+ * This is another weak sign that the desired behavior is to shape the entire paragraph before
splitting to lines.
+ *
+ * We only perform shaping in implicit mode, for two reasons:
+ *
+ * Following the CSS logic, I think the sensible behavior for a partially visible word (e.g. at the
margin of a
+ * text editor) is to use the joining/shaping form according to the entire word. Hence in explicit
mode it must be
+ * the responsibility of the BiDi-aware application and not the terminal emulator to perform
joining/shaping.
+ *
+ * And a technical limitation: FriBidi can only perform joining/shaping with the logical order as
input, not with
+ * the visual order. We'd need to find another API, or do ugly workarounds, which I'd rather not. */
+ fribidi_join_arabic (fribidi_chartypes, count, fribidi_levels, fribidi_joiningtypes);
+ fribidi_shape_arabic (FRIBIDI_FLAGS_ARABIC, fribidi_levels, count, fribidi_joiningtypes,
fribidi_chars);
+
+ g_assert_cmpint (pbase_dir, !=, FRIBIDI_PAR_ON);
+ /* For convenience, from now on this variable contains the resolved (i.e. possibly autodetected)
value. */
+ rtl = (pbase_dir == FRIBIDI_PAR_RTL || pbase_dir == FRIBIDI_PAR_WRTL);
+
+ if (!rtl && level == 1) {
+ /* Fast shortcut for LTR-only paragraphs. */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return explicit_paragraph (row_orig, false);
+ }
+
+ /* Reshuffle line by line. */
+ row = row_orig;
+ line = 0;
+ if (G_UNLIKELY (row < m_start)) {
+ line = m_start - row;
+ row = m_start;
+ }
+
+ while (row < _vte_ring_next(m_ring) && row < m_start + m_len) {
+ bidirow = get_row_map_writable(row);
+ bidirow->m_base_rtl = rtl;
+ bidirow->m_has_foreign = true;
+ bidirow->set_width(m_width);
+
+ row_data = m_ring->index_safe(row);
+ if (row_data == nullptr)
+ break;
+
+ level = fribidi_reorder_line (FRIBIDI_FLAGS_DEFAULT,
+ fribidi_chartypes,
+ lines[line + 1] - lines[line],
+ lines[line],
+ pbase_dir,
+ fribidi_levels,
+ NULL,
+ fribidi_map);
+
+ if (level == 0) {
+ /* error, what should we do? */
+ explicit_line (row, rtl);
+ bidirow->m_has_foreign = true;
+ goto next_line;
+ }
+
+ if (!rtl && level == 1) {
+ /* Fast shortcut for LTR-only lines. */
+ explicit_line (row, false);
+ bidirow->m_has_foreign = true;
+ goto next_line;
+ }
+
+ /* Copy to our realm. Proceed in visual order.*/
+ tv = 0;
+ if (rtl) {
+ /* Unused cells on the left for RTL paragraphs */
+ int unused = MAX(m_width - row_data->len, 0);
+ for (; tv < unused; tv++) {
+ bidirow->m_vis2log[tv] = m_width - 1 - tv;
+ bidirow->m_vis_rtl[tv] = true;
+ bidirow->m_vis_shaped_char[tv] = 0;
+ }
+ }
+ for (fv = lines[line]; fv < lines[line + 1]; fv++) {
+ /* Inflate fribidi's result by inserting fragments. */
+ fl = fribidi_map[fv];
+ if (fl == -1)
+ continue;
+ tl = fribidi_to_term[fl];
+ cell = _vte_row_data_get (row_data, tl);
+ g_assert (!cell->attr.fragment());
+ g_assert (cell->attr.columns() > 0);
+ if (FRIBIDI_LEVEL_IS_RTL(fribidi_levels[fl])) {
+ /* RTL character directionality. Map fragments in reverse order. */
+ for (col = 0; col < cell->attr.columns(); col++) {
+ bidirow->m_vis2log[tv + col] = tl + cell->attr.columns() - 1 - col;
+ bidirow->m_vis_rtl[tv + col] = true;
+ bidirow->m_vis_shaped_char[tv + col] = fribidi_chars[fl];
+ }
+ tv += cell->attr.columns();
+ tl += cell->attr.columns();
+ } else {
+ /* LTR character directionality. */
+ for (col = 0; col < cell->attr.columns(); col++) {
+ bidirow->m_vis2log[tv] = tl;
+ bidirow->m_vis_rtl[tv] = false;
+ bidirow->m_vis_shaped_char[tv] = fribidi_chars[fl];
+ tv++;
+ tl++;
+ }
+ }
+ }
+ if (!rtl) {
+ /* Unused cells on the right for LTR paragraphs */
+ g_assert_cmpint (tv, ==, MIN (row_data->len, m_width));
+ for (; tv < m_width; tv++) {
+ bidirow->m_vis2log[tv] = tv;
+ bidirow->m_vis_rtl[tv] = false;
+ bidirow->m_vis_shaped_char[tv] = 0;
+ }
+ }
+ g_assert_cmpint (tv, ==, m_width);
+
+ /* From vis2log create the log2vis mapping too.
+ * In debug mode assert that we have a bijective mapping. */
+ if (_vte_debug_on (VTE_DEBUG_BIDI)) {
+ for (tl = 0; tl < m_width; tl++) {
+ bidirow->m_log2vis[tl] = -1;
+ }
+ }
+
+ for (tv = 0; tv < m_width; tv++) {
+ bidirow->m_log2vis[bidirow->m_vis2log[tv]] = tv;
+ }
+
+ if (_vte_debug_on (VTE_DEBUG_BIDI)) {
+ for (tl = 0; tl < m_width; tl++) {
+ g_assert_cmpint (bidirow->m_log2vis[tl], !=, -1);
+ }
+ }
+
+next_line:
+ line++;
+ row++;
+
+ if (!row_data->attr.soft_wrapped)
+ break;
+ }
+
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+
+ return row;
+#endif /* !WITH_FRIBIDI */
+}
+
+
+/* Find the mirrored counterpart of a codepoint, just like
+ * fribidi_get_mirror_char() or g_unichar_get_mirror_char() does.
+ * Two additions:
+ * - works with vteunistr, that is, preserves combining accents;
+ * - optionally mirrors box drawing characters.
+ */
+gboolean vte_bidi_get_mirror_char (vteunistr unistr, gboolean mirror_box_drawing, vteunistr *out)
+{
+ static const unsigned char mirrored_2500[0x80] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x11, 0x12,
0x13,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x24, 0x25, 0x26,
0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2e, 0x2d,
0x2f,
+ 0x30, 0x32, 0x31, 0x33, 0x34, 0x36, 0x35, 0x37, 0x38, 0x3a, 0x39, 0x3b, 0x3c, 0x3e, 0x3d,
0x3f,
+ 0x40, 0x41, 0x42, 0x44, 0x43, 0x46, 0x45, 0x47, 0x48, 0x4a, 0x49, 0x4b, 0x4c, 0x4d, 0x4e,
0x4f,
+ 0x50, 0x51, 0x55, 0x56, 0x57, 0x52, 0x53, 0x54, 0x5b, 0x5c, 0x5d, 0x58, 0x59, 0x5a, 0x61,
0x62,
+ 0x63, 0x5e, 0x5f, 0x60, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6e, 0x6d,
0x70,
+ 0x6f, 0x72, 0x71, 0x73, 0x76, 0x75, 0x74, 0x77, 0x7a, 0x79, 0x78, 0x7b, 0x7e, 0x7d, 0x7c,
0x7f };
+
+ gunichar base_ch = _vte_unistr_get_base (unistr);
+ gunichar base_ch_mirrored = base_ch;
+
+ if (G_UNLIKELY (base_ch >= 0x2500 && base_ch < 0x2580)) {
+ if (G_UNLIKELY (mirror_box_drawing))
+ base_ch_mirrored = 0x2500 + mirrored_2500[base_ch - 0x2500];
+ } else {
+#ifdef WITH_FRIBIDI
+ /* Prefer the FriBidi variant as that's more likely to be in sync with the rest of our BiDi
stuff. */
+ fribidi_get_mirror_char (base_ch, &base_ch_mirrored);
+#else
+ /* Fall back to glib, so that we still get mirrored characters in explicit RTL mode without
BiDi support. */
+ g_unichar_get_mirror_char (base_ch, &base_ch_mirrored);
+#endif
+ }
+
+ vteunistr unistr_mirrored = _vte_unistr_replace_base (unistr, base_ch_mirrored);
+
+ if (out)
+ *out = unistr_mirrored;
+ return unistr_mirrored == unistr;
+}
diff --git a/src/bidi.hh b/src/bidi.hh
new file mode 100644
index 00000000..38e8035e
--- /dev/null
+++ b/src/bidi.hh
@@ -0,0 +1,124 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#include "ring.hh"
+#include "vterowdata.hh"
+#include "vtetypes.hh"
+#include "vteunistr.h"
+
+namespace vte {
+
+namespace base { // FIXME ???
+
+/* BidiRow contains the BiDi transformation of a single row. */
+class BidiRow {
+ friend class RingView;
+
+public:
+ BidiRow();
+ ~BidiRow();
+
+ // prevent accidents
+ BidiRow(BidiRow& o) = delete;
+ BidiRow(BidiRow const& o) = delete;
+ BidiRow(BidiRow&& o) = delete;
+ BidiRow& operator= (BidiRow& o) = delete;
+ BidiRow& operator= (BidiRow const& o) = delete;
+ BidiRow& operator= (BidiRow&& o) = delete;
+
+ vte::grid::column_t log2vis(vte::grid::column_t col) const;
+ vte::grid::column_t vis2log(vte::grid::column_t col) const;
+ bool log_is_rtl(vte::grid::column_t col) const;
+ bool vis_is_rtl(vte::grid::column_t col) const;
+ vteunistr vis_get_shaped_char(vte::grid::column_t col, vteunistr s) const;
+ bool base_is_rtl() const;
+ bool has_foreign() const;
+
+private:
+ void set_width(vte::grid::column_t width);
+
+ vte::grid::column_t m_width;
+ vte::grid::column_t m_width_alloc;
+
+ vte::grid::column_t *m_log2vis;
+ vte::grid::column_t *m_vis2log;
+ guint8 *m_vis_rtl;
+ gunichar *m_vis_shaped_char;
+
+ guint8 m_base_rtl: 1;
+ guint8 m_has_foreign: 1;
+};
+
+
+/* RingView contains the BiDi transformations for all the rows of the viewport. */
+class RingView {
+public:
+ RingView();
+ ~RingView();
+
+ // prevent accidents
+ RingView(RingView& o) = delete;
+ RingView(RingView const& o) = delete;
+ RingView(RingView&& o) = delete;
+ RingView& operator= (RingView& o) = delete;
+ RingView& operator= (RingView const& o) = delete;
+ RingView& operator= (RingView&& o) = delete;
+
+ void set_ring(Ring *ring);
+ void set_rows(vte::grid::row_t start, vte::grid::row_t len);
+ void set_width(vte::grid::column_t width);
+
+ inline void invalidate() { m_invalid = true; }
+ void maybe_update();
+
+ BidiRow const* get_row_map(vte::grid::row_t row) const;
+
+private:
+ Ring *m_ring;
+
+ BidiRow **m_bidirows;
+
+ vte::grid::row_t m_start;
+ vte::grid::row_t m_len;
+ vte::grid::column_t m_width;
+
+ vte::grid::row_t m_height_alloc;
+
+ bool m_invalid;
+
+ BidiRow* get_row_map_writable(vte::grid::row_t row) const;
+
+ void explicit_line(vte::grid::row_t row, bool rtl);
+ vte::grid::row_t explicit_paragraph(vte::grid::row_t row, bool rtl);
+ vte::grid::row_t find_paragraph(vte::grid::row_t row);
+ vte::grid::row_t paragraph(vte::grid::row_t row);
+};
+
+}; /* namespace base */
+
+}; /* namespace vte */
+
+G_BEGIN_DECLS
+
+gboolean vte_bidi_get_mirror_char (vteunistr unistr, gboolean mirror_box_drawing, vteunistr
*unistr_mirrored);
+
+G_END_DECLS
diff --git a/src/debug.cc b/src/debug.cc
index 5d88833b..afbd7413 100644
--- a/src/debug.cc
+++ b/src/debug.cc
@@ -57,6 +57,7 @@ _vte_debug_init(void)
{ "hyperlink", VTE_DEBUG_HYPERLINK },
{ "modes", VTE_DEBUG_MODES },
{ "emulation", VTE_DEBUG_EMULATION },
+ { "bidi", VTE_DEBUG_BIDI },
};
_vte_debug_flags = g_parse_debug_string (g_getenv("VTE_DEBUG"),
diff --git a/src/debug.h b/src/debug.h
index 41c3665d..92d9d0f9 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -64,6 +64,7 @@ typedef enum {
VTE_DEBUG_HYPERLINK = 1 << 24,
VTE_DEBUG_MODES = 1 << 25,
VTE_DEBUG_EMULATION = 1 << 26,
+ VTE_DEBUG_BIDI = 1 << 27,
} VteDebugFlags;
void _vte_debug_init(void);
diff --git a/src/meson.build b/src/meson.build
index 29046b77..fef7da1e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -56,6 +56,8 @@ utf8_sources = files(
libvte_common_sources = debug_sources + modes_sources + parser_sources + utf8_sources + files(
'attr.hh',
+ 'bidi.cc',
+ 'bidi.hh',
'buffer.h',
'caps.hh',
'cell.hh',
@@ -144,6 +146,7 @@ libvte_common_public_deps = [
]
libvte_common_deps = libvte_common_public_deps + [
+ fribidi_dep,
gnutls_dep,
pcre2_dep,
libm_dep,
diff --git a/src/modes-ecma.hh b/src/modes-ecma.hh
index 617d2985..9c767ac1 100644
--- a/src/modes-ecma.hh
+++ b/src/modes-ecma.hh
@@ -38,6 +38,20 @@
*/
MODE(IRM, 4)
+/*
+ * BDSM - Bi-Directional Support Mode
+ *
+ * Reset state is explicit mode, set state is implicit mode
+ *
+ * References: ECMA-48
+ * ECMA TR/53
+ * [FIXME link to our spec]
+ *
+ * Default in ECMA: reset
+ * Default in VTE: set
+ */
+MODE(BDSM, 8)
+
/*
* SRM - local echo send/receive mode
* If reset, characters entered by the keyboard are shown on the
@@ -59,8 +73,7 @@ MODE_FIXED(CRM, 3, ALWAYS_RESET)
MODE_FIXED(SRTM, 5, ALWAYS_RESET)
MODE_FIXED(ERM, 6, ALWAYS_RESET)
MODE_FIXED(VEM, 7, ALWAYS_RESET)
-MODE_FIXED(BDSM, 8, ALWAYS_RESET)
-MODE_FIXED(DCSM, 9, ALWAYS_RESET)
+MODE_FIXED(DCSM, 9, ALWAYS_SET)
MODE_FIXED(HEM, 10, ALWAYS_RESET)
MODE_FIXED(PUM, 11, ALWAYS_RESET) /* ECMA-48 § F.4.1 Deprecated */
MODE_FIXED(FEAM, 13, ALWAYS_RESET)
diff --git a/src/modes-private.hh b/src/modes-private.hh
index eb313823..80bc80d2 100644
--- a/src/modes-private.hh
+++ b/src/modes-private.hh
@@ -142,6 +142,32 @@ MODE(XTERM_READLINE_BRACKETED_PASTE, 2004)
MODE(URXVT_MOUSE_EXT, 1015)
+/* VTE */
+
+/*
+ * Whether box drawing characters in the U+2500..U+257F range
+ * are to be mirrored in RTL context.
+ *
+ * The (temporary) choice of number 2500 is a misuse of hex 2500
+ * as a decimal number, but is supposed to be easily memorable.
+ */
+MODE(VTE_BOX_DRAWING_MIRROR, 2500)
+
+/*
+ * Whether BiDi paragraph direction is autodetected.
+ *
+ * The number choice is temporary.
+ */
+MODE(VTE_BIDI_AUTO, 2501)
+
+/*
+ * Whether to swap the Left and Right arrow keys if the cursor
+ * stands over an RTL paragraphs.
+ *
+ * The number choice is not necessarily final.
+ */
+MODE(VTE_BIDI_SWAP_ARROW_KEYS, 1243)
+
/* Not supported modes: */
/* DEC */
diff --git a/src/modes.hh b/src/modes.hh
index 47d0b415..af218622 100644
--- a/src/modes.hh
+++ b/src/modes.hh
@@ -170,7 +170,8 @@ public:
#undef MODE
#undef MODE_FIXED
- constexpr ECMA() : Self{eSRM} { }
+ constexpr ECMA() : Self{eBDSM,
+ eSRM} { }
}; // class ECMA
@@ -226,6 +227,7 @@ public:
constexpr Private() : Self{eDEC_AUTOWRAP,
eDEC_TEXT_CURSOR,
+ eVTE_BIDI_SWAP_ARROW_KEYS,
eXTERM_ALTBUF_SCROLL,
eXTERM_META_SENDS_ESCAPE} { }
diff --git a/src/parser-cmd.hh b/src/parser-cmd.hh
index 6fcaacc2..c562d7c0 100644
--- a/src/parser-cmd.hh
+++ b/src/parser-cmd.hh
@@ -104,11 +104,13 @@ _VTE_CMD(RM_DEC) /* reset mode dec */
_VTE_CMD(RM_ECMA) /* reset mode ecma */
_VTE_CMD(SCORC) /* SCO restore cursor */
_VTE_CMD(SCOSC) /* SCO save cursor */
+_VTE_CMD(SCP) /* select character path */
_VTE_CMD(SD) /* scroll down */
_VTE_CMD(SD_OR_XTERM_IHMT) /* scroll down or xterm initiate highlight mouse tracking */
_VTE_CMD(SGR) /* select graphics rendition */
_VTE_CMD(SM_DEC) /* set mode dec */
_VTE_CMD(SM_ECMA) /* set mode ecma */
+_VTE_CMD(SPD) /* select presentation directions */
_VTE_CMD(SS2) /* single shift 2 */
_VTE_CMD(SS3) /* single shift 3 */
_VTE_CMD(SUB) /* substitute */
@@ -276,7 +278,6 @@ _VTE_NOP(RLOGIN_MML) /* RLogin music macro language */
_VTE_NOP(SACS) /* set additional character separation */
_VTE_NOP(SAPV) /* select alternative presentation variants */
_VTE_NOP(SCO) /* select character orientation */
-_VTE_NOP(SCP) /* select character path */
_VTE_NOP(SCS) /* set character spacing */
_VTE_NOP(SDS) /* start directed string */
_VTE_NOP(SEE) /* select editing extent */
@@ -289,7 +290,6 @@ _VTE_NOP(SL) /* scroll left */
_VTE_NOP(SLS) /* set line spacing */
_VTE_NOP(SOH) /* start of heading */
_VTE_NOP(SPA) /* start of protected area */
-_VTE_NOP(SPD) /* select presentation directions */
_VTE_NOP(SPH) /* set page home */
_VTE_NOP(SPI) /* spacing increment */
_VTE_NOP(SPL) /* set page limit */
diff --git a/src/parser-csi.hh b/src/parser-csi.hh
index 410d533f..53e1f1f8 100644
--- a/src/parser-csi.hh
+++ b/src/parser-csi.hh
@@ -59,7 +59,7 @@ _VTE_NOQ(SEE, CSI, 'Q', NONE, 0, NONE ) /* select ed
_VTE_NOQ(PPR, CSI, 'Q', NONE, 1, SPACE ) /* page-position-relative */
_VTE_NOQ(PPB, CSI, 'R', NONE, 1, SPACE ) /* page-position-backward */
_VTE_SEQ(SU, CSI, 'S', NONE, 0, NONE ) /* scroll-up */
-_VTE_NOQ(SPD, CSI, 'S', NONE, 1, SPACE ) /* select presentation directions */
+_VTE_SEQ(SPD, CSI, 'S', NONE, 1, SPACE ) /* select presentation directions */
_VTE_NOQ(XTERM_SGFX, CSI, 'S', WHAT, 0, NONE ) /* xterm-sixel-graphics */
_VTE_SEQ(SD_OR_XTERM_IHMT, CSI, 'T', NONE, 0, NONE ) /* scroll-down or
xterm-initiate-highlight-mouse-tracking */
_VTE_NOQ(DTA, CSI, 'T', NONE, 1, SPACE ) /* dimension text area */
@@ -114,7 +114,7 @@ _VTE_NOQ(MC_DEC, CSI, 'i', WHAT, 0, NONE ) /* media-cop
_VTE_NOQ(HPB, CSI, 'j', NONE, 0, NONE ) /* horizontal position backward */
_VTE_NOQ(SPL, CSI, 'j', NONE, 1, SPACE ) /* set page limit */
_VTE_NOQ(VPB, CSI, 'k', NONE, 0, NONE ) /* line position backward */
-_VTE_NOQ(SCP, CSI, 'k', NONE, 1, SPACE ) /* select character path */
+_VTE_SEQ(SCP, CSI, 'k', NONE, 1, SPACE ) /* select character path */
_VTE_SEQ(RM_ECMA, CSI, 'l', NONE, 0, NONE ) /* reset-mode-ecma */
_VTE_SEQ(RM_DEC, CSI, 'l', WHAT, 0, NONE ) /* reset-mode-dec */
_VTE_SEQ(SGR, CSI, 'm', NONE, 0, NONE ) /* select-graphics-rendition */
diff --git a/src/ring.cc b/src/ring.cc
index d7a3bde1..93b8d8b0 100644
--- a/src/ring.cc
+++ b/src/ring.cc
@@ -373,6 +373,7 @@ Ring::freeze_row(row_t position,
if (!row->attr.soft_wrapped)
g_string_append_c (buffer, '\n');
record.soft_wrapped = row->attr.soft_wrapped;
+ record.bidi_flags = row->attr.bidi_flags;
_vte_stream_append(m_text_stream, buffer->str, buffer->len);
append_row_record(&record, position);
@@ -435,6 +436,7 @@ Ring::thaw_row(row_t position,
g_string_truncate (buffer, buffer->len - 1);
else
row->attr.soft_wrapped = TRUE;
+ row->attr.bidi_flags = records[0].bidi_flags;
p = buffer->str;
end = p + buffer->len;
@@ -607,6 +609,15 @@ Ring::index(row_t position)
return &m_cached_row;
}
+VteRowData const*
+Ring::index_safe(row_t position)
+{
+ if (G_UNLIKELY (position < m_start || position >= m_end))
+ return nullptr;
+
+ return index(position);
+}
+
/*
* Returns the hyperlink idx at the given position.
*
@@ -849,7 +860,7 @@ Ring::shrink(row_t max_len)
* Return: the newly added row.
*/
VteRowData*
-Ring::insert(row_t position)
+Ring::insert(row_t position, guint8 bidi_flags)
{
row_t i;
VteRowData* row, tmp;
@@ -871,6 +882,7 @@ Ring::insert(row_t position)
*get_writable_index(position) = tmp;
row = get_writable_index(position);
+ row->attr.bidi_flags = bidi_flags;
_vte_row_data_clear (row);
m_end++;
@@ -921,9 +933,9 @@ Ring::remove(row_t position)
* Return: the newly added row.
*/
VteRowData*
-Ring::append()
+Ring::append(guint8 bidi_flags)
{
- return insert(next());
+ return insert(next(), bidi_flags);
}
@@ -1187,6 +1199,7 @@ Ring::rewrap(column_t columns,
/* Find the boundaries of the next paragraph */
gboolean prev_record_was_soft_wrapped = FALSE;
gboolean paragraph_is_ascii = TRUE;
+ guint8 paragraph_bidi_flags = old_record.bidi_flags;
gsize text_offset = paragraph_start_text_offset;
RowRecord new_record;
column_t col = 0;
@@ -1234,6 +1247,7 @@ Ring::rewrap(column_t columns,
new_record.text_start_offset = text_offset;
new_record.attr_start_offset = attr_offset;
new_record.is_ascii = paragraph_is_ascii;
+ new_record.bidi_flags = paragraph_bidi_flags;
while (paragraph_len > 0) {
/* Wrap one continuous run of identical attributes within the paragraph. */
diff --git a/src/ring.hh b/src/ring.hh
index 1b17ca7a..831a55da 100644
--- a/src/ring.hh
+++ b/src/ring.hh
@@ -73,6 +73,7 @@ public:
//FIXMEchpe rename this to at()
//FIXMEchpe use references not pointers
VteRowData const* index(row_t position); /* const? */
+ VteRowData const* index_safe(row_t position);
VteRowData* index_writable(row_t position);
void hyperlink_maybe_gc(row_t increment);
@@ -85,8 +86,8 @@ public:
row_t reset();
void resize(row_t max_rows = kDefaultMaxRows);
void shrink(row_t max_len = kDefaultMaxRows);
- VteRowData* insert(row_t position);
- VteRowData* append();
+ VteRowData* insert(row_t position, guint8 bidi_flags);
+ VteRowData* append(guint8 bidi_flags);
void remove(row_t position);
void drop_scrollback(row_t position);
void set_visible_rows(row_t rows);
@@ -120,6 +121,7 @@ private:
size_t attr_start_offset; /* offset of the first character's attributes */
int soft_wrapped: 1; /* end of line is not '\n' */
int is_ascii: 1; /* for rewrapping speedup: guarantees that line contains 32..126
bytes only. Can be 0 even when ascii only. */
+ guint8 bidi_flags: 4;
} RowRecord;
static_assert(std::is_pod<RowRecord>::value, "Ring::RowRecord is not POD");
@@ -248,8 +250,8 @@ static inline auto _vte_ring_get_hyperlink_at_position (VteRing *ring, gulong po
static inline long _vte_ring_reset (VteRing *ring) { return ring->reset(); }
static inline void _vte_ring_resize (VteRing *ring, gulong max_rows) { ring->resize(max_rows); }
static inline void _vte_ring_shrink (VteRing *ring, gulong max_len) { ring->shrink(max_len); }
-static inline VteRowData *_vte_ring_insert (VteRing *ring, gulong position) { return ring->insert(position);
}
-static inline VteRowData *_vte_ring_append (VteRing *ring) { return ring->append(); }
+static inline VteRowData *_vte_ring_insert (VteRing *ring, gulong position, guint8 bidi_flags) { return
ring->insert(position, bidi_flags); }
+static inline VteRowData *_vte_ring_append (VteRing *ring, guint8 bidi_flags) { return
ring->append(bidi_flags); }
static inline void _vte_ring_remove (VteRing *ring, gulong position) { ring->remove(position); }
static inline void _vte_ring_drop_scrollback (VteRing *ring, gulong position) {
ring->drop_scrollback(position); }
static inline void _vte_ring_set_visible_rows (VteRing *ring, gulong rows) { ring->set_visible_rows(rows); }
diff --git a/src/vte.cc b/src/vte.cc
index b02b673d..c92adab8 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -36,6 +36,7 @@
#include <vte/vte.h>
#include "vteinternal.hh"
+#include "bidi.hh"
#include "buffer.h"
#include "debug.h"
#include "vtedraw.hh"
@@ -139,11 +140,11 @@ Terminal::ring_insert(vte::grid::row_t position,
bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);
while (G_UNLIKELY (_vte_ring_next (ring) < position)) {
- row = _vte_ring_append (ring);
+ row = _vte_ring_append (ring, get_bidi_flags());
if (not_default_bg)
_vte_row_data_fill (row, &m_fill_defaults, m_column_count);
}
- row = _vte_ring_insert (ring, position);
+ row = _vte_ring_insert (ring, position, get_bidi_flags());
if (fill && not_default_bg)
_vte_row_data_fill (row, &m_fill_defaults, m_column_count);
return row;
@@ -263,6 +264,13 @@ Terminal::invalidate_rows(vte::grid::row_t row_start,
row_start, row_end);
_vte_debug_print (VTE_DEBUG_WORK, "?");
+ // HACK for BiDi: Always invalidate everything.
+ // In fact we'd need to invalidate the entire implicit paragraph.
+ if (TRUE) {
+ invalidate_all();
+ return;
+ }
+
/* Scrolled back, visible parts didn't change. */
if (row_start > last_displayed_row())
return;
@@ -388,7 +396,7 @@ Terminal::invalidate_all()
/* Find the row in the given position in the backscroll buffer.
* Note that calling this method may invalidate the return value of
* a previous find_row_data() call. */
-// FIXMEchpe replace this with a method on VteRing
+// FIXMEchpe replace this with a method on VteRing (index_safe())
VteRowData const*
Terminal::find_row_data(vte::grid::row_t row) const
{
@@ -1540,9 +1548,22 @@ Terminal::grid_coords_from_view_coords(vte::view::coords const& pos) const
vte::grid::row_t row = pixel_to_row(pos.y);
+ /* BiDi: convert to logical column. */
+ vte::base::BidiRow const* bidirow = m_ringview.get_row_map(confine_grid_row(row));
+ col = bidirow->vis2log(col);
+
return vte::grid::coords(row, col);
}
+vte::grid::row_t
+Terminal::confine_grid_row(vte::grid::row_t const& row) const
+{
+ auto first_row = first_displayed_row();
+ auto last_row = last_displayed_row();
+
+ return CLAMP(row, first_row, last_row);
+}
+
/*
* Terminal::confined_grid_coords_from_view_coords:
* @pos: the view coordinates
@@ -1604,34 +1625,46 @@ Terminal::confine_grid_coords(vte::grid::coords const& rowcol) const
/*
* Track mouse click and drag positions (the "origin" and "last" coordinates) with half cell accuracy,
- * that is, know whether the event occurred over the left or right half of the cell.
+ * that is, know whether the event occurred over the left/start or right/end half of the cell.
* This is required because some selection modes care about the cell over which the event occurred,
* while some care about the closest boundary between cells.
*
* Storing the actual view coordinates would become problematic when the font size changes (bug 756058),
* and would cause too much work when the mouse moves within the half cell.
*
- * Left margin or anything further to the left is denoted by column -1's right half,
- * right margin or anything further to the right is denoted by column m_column_count's left half.
+ * Left/start margin or anything further to the left/start is denoted by column -1's right half,
+ * right/end margin or anything further to the right/end is denoted by column m_column_count's left half.
+ *
+ * BiDi: returns logical (start/end) position for normal selection modes, visual (left/right) position for
block mode.
*/
vte::grid::halfcoords
Terminal::selection_grid_halfcoords_from_view_coords(vte::view::coords const& pos) const
{
vte::grid::row_t row = pixel_to_row(pos.y);
- vte::grid::halfcolumn_t halfcolumn;
+ vte::grid::column_t col;
+ vte::grid::half_t half;
if (pos.x < 0) {
- halfcolumn.set_column(-1);
- halfcolumn.set_half(1);
+ col = -1;
+ half = 1;
} else if (pos.x >= m_column_count * m_cell_width) {
- halfcolumn.set_column(m_column_count);
- halfcolumn.set_half(0);
+ col = m_column_count;
+ half = 0;
} else {
- halfcolumn.set_column(pos.x / m_cell_width);
- halfcolumn.set_half((pos.x * 2 / m_cell_width) % 2);
+ col = pos.x / m_cell_width;
+ half = (pos.x * 2 / m_cell_width) % 2;
}
- return { row, halfcolumn };
+ if (!m_selection_block_mode) {
+ /* BiDi: convert from visual to logical half column. */
+ vte::base::BidiRow const* bidirow = m_ringview.get_row_map(confine_grid_row(row));
+
+ if (bidirow->vis_is_rtl(col))
+ half = 1 - half;
+ col = bidirow->vis2log(col);
+ }
+
+ return { row, vte::grid::halfcolumn_t(col, half) };
}
/*
@@ -1648,6 +1681,8 @@ Terminal::selection_maybe_swap_endpoints(vte::view::coords const& pos)
if (m_selection_resolved.empty())
return;
+ ringview_maybe_update();
+
auto current = selection_grid_halfcoords_from_view_coords (pos);
if (m_selection_block_mode) {
@@ -2894,6 +2929,8 @@ Terminal::insert_char(gunichar c,
row = ensure_row();
set_soft_wrapped(m_screen->cursor.row);
cursor_down(false);
+ ensure_row();
+ apply_bidi_attributes(m_screen->cursor.row, row->attr.bidi_flags, VTE_BIDI_ALL);
} else {
/* Don't wrap, stay at the rightmost column. */
col = m_screen->cursor.col =
@@ -3030,6 +3067,72 @@ not_inserted:
m_line_wrapped = line_wrapped;
}
+guint8
+Terminal::get_bidi_flags()
+{
+ return (m_modes_ecma.BDSM() ? VTE_BIDI_IMPLICIT : 0) |
+ (m_bidi_rtl ? VTE_BIDI_RTL : 0) |
+ (m_modes_private.VTE_BIDI_AUTO() ? VTE_BIDI_AUTO : 0) |
+ (m_modes_private.VTE_BOX_DRAWING_MIRROR() ? VTE_BIDI_BOX_MIRROR : 0);
+}
+
+/* Apply the specified BiDi parameters on the paragraph beginning at the specified line. */
+void
+Terminal::apply_bidi_attributes(vte::grid::row_t row, guint8 bidi_flags, guint8 bidi_flags_mask)
+{
+ VteRowData *rowdata;
+
+ bidi_flags &= bidi_flags_mask;
+
+ while (true) {
+ rowdata = _vte_ring_index_writable (m_screen->row_data, row);
+ if (rowdata == nullptr)
+ return;
+
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "Applying BiDi parameters on row %ld.\n", row);
+
+ rowdata->attr.bidi_flags &= ~bidi_flags_mask;
+ rowdata->attr.bidi_flags |= bidi_flags;
+
+ invalidate_row(row);
+ if (!rowdata->attr.soft_wrapped)
+ return;
+ row++;
+ }
+}
+
+/* Apply the current BiDi parameters covered by bidi_flags_mask on the current paragraph
+ * if the cursor is at the first position of this paragraph. */
+void
+Terminal::maybe_apply_bidi_attributes(guint8 bidi_flags_mask)
+{
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "Maybe applying BiDi parameters on current paragraph.\n");
+
+ if (m_screen->cursor.col != 0) {
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "No, cursor not in first column.\n");
+ return;
+ }
+
+ auto row = m_screen->cursor.row;
+
+ if (row > _vte_ring_delta (m_screen->row_data)) {
+ const VteRowData *rowdata = _vte_ring_index (m_screen->row_data, row - 1);
+ if (rowdata != nullptr && rowdata->attr.soft_wrapped) {
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "No, we're not after a hard wrap.\n");
+ return;
+ }
+ }
+
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "Yes, applying.\n");
+
+ apply_bidi_attributes (row, get_bidi_flags(), bidi_flags_mask);
+}
+
static void
reaper_child_exited_cb(VteReaper *reaper,
int ipid,
@@ -3773,6 +3876,11 @@ Terminal::process_incoming()
queue_contents_changed();
}
+ /* BiDi properties might have changed, even when !modified.
+ * emit_pending_signals() requires the ringview to be updated. */
+ m_ringview.invalidate();
+ ringview_maybe_update();
+
emit_pending_signals();
if (invalidated_text) {
@@ -4888,6 +4996,39 @@ Terminal::widget_key_press(GdkEventKey *event)
/* If the above switch statement didn't do the job, try mapping
* it to a literal or capability name. */
if (handled == FALSE) {
+ /* In keyboard arrow swapping mode, the left and right arrows
+ * are swapped if the cursor stands inside an RTL paragraph. */
+ if (m_modes_private.VTE_BIDI_SWAP_ARROW_KEYS() &&
+ (keyval == GDK_KEY_Left ||
+ keyval == GDK_KEY_Right ||
+ keyval == GDK_KEY_KP_Left ||
+ keyval == GDK_KEY_KP_Right)) {
+ /* m_ringview is for the onscreen contents and the cursor may be
+ * offscreen, so use a temporary ringview for the cursor's row. */
+ vte::base::RingView *ringview = new vte::base::RingView();
+ ringview->set_ring(m_screen->row_data);
+ ringview->set_rows(m_screen->cursor.row, 1);
+ ringview->set_width(m_column_count);
+ ringview->maybe_update();
+ if (ringview->get_row_map(m_screen->cursor.row)->base_is_rtl()) {
+ switch (keyval) {
+ case GDK_KEY_Left:
+ keyval = GDK_KEY_Right;
+ break;
+ case GDK_KEY_Right:
+ keyval = GDK_KEY_Left;
+ break;
+ case GDK_KEY_KP_Left:
+ keyval = GDK_KEY_KP_Right;
+ break;
+ case GDK_KEY_KP_Right:
+ keyval = GDK_KEY_KP_Left;
+ break;
+ }
+ }
+ delete ringview;
+ }
+
_vte_keymap_map(keyval, m_modifiers,
m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
m_modes_private.DEC_APPLICATION_KEYPAD(),
@@ -5130,7 +5271,7 @@ Terminal::line_is_wrappable(vte::grid::row_t row) const
* In block mode, similarly to char mode, we care about vertical character boundary. (This is somewhat
* debatable, as results in asymmetrical behavior along the two axes: a rectangle can disappear by
* becoming zero wide, but not zero high.) We cannot take care of CJKs at the endpoints now because CJKs
- * can cross the boundary in any included row. Taking care of them needs to go to cell_is_selected().
+ * can cross the boundary in any included row. Taking care of them needs to go to cell_is_selected_vis().
* We don't care about used vs. unused cells either. The event coordinate is simply rounded to the
* nearest vertical cell boundary.
*/
@@ -5414,6 +5555,8 @@ Terminal::modify_selection (vte::view::coords const& pos)
{
g_assert (m_selecting);
+ ringview_maybe_update();
+
auto current = selection_grid_halfcoords_from_view_coords (pos);
if (current == m_selection_last)
@@ -5427,21 +5570,29 @@ Terminal::modify_selection (vte::view::coords const& pos)
resolve_selection();
}
-/* Check if a cell is selected or not. */
+/* Check if a cell is selected or not. BiDi: the coordinate is visual. */
bool
-Terminal::cell_is_selected(vte::grid::column_t col,
- vte::grid::row_t row) const
+Terminal::cell_is_selected_vis(vte::grid::column_t col,
+ vte::grid::row_t row) const
{
if (m_selection_block_mode) {
/* In block mode, make sure CJKs and TABs aren't cut in half. */
+ /* BiDi: convert to logical column... */
+ vte::base::BidiRow const* bidirow = m_ringview.get_row_map(row);
+ col = bidirow->vis2log(col);
while (col > 0) {
VteCell const* cell = find_charcell(col, row);
if (!cell || !cell->attr.fragment())
break;
col--;
}
+ /* ... and back to visual. */
+ col = bidirow->log2vis(col);
return m_selection_resolved.box_contains ({ row, col });
} else {
+ /* BiDi: convert to logical column. */
+ vte::base::BidiRow const* bidirow = m_ringview.get_row_map(row);
+ col = bidirow->vis2log(col);
/* In normal modes, resolve_selection() made sure to generate such boundaries for
m_selection_resolved. */
return m_selection_resolved.contains ({ row, col });
}
@@ -5902,6 +6053,10 @@ Terminal::match_hilite_update()
glong col = pos.x / m_cell_width;
glong row = pixel_to_row(pos.y);
+ /* BiDi: convert to logical column. */
+ vte::base::BidiRow const* bidirow = m_ringview.get_row_map(confine_grid_row(row));
+ col = bidirow->vis2log(col);
+
_vte_debug_print(VTE_DEBUG_EVENTS,
"Match hilite update (%ld, %ld) -> %ld, %ld\n",
pos.x, pos.y, col, row);
@@ -6106,6 +6261,9 @@ Terminal::get_text(vte::grid::row_t start_row,
GString *string;
struct _VteCharAttributes attr;
vte::color::rgb fore, back;
+ vte::base::RingView *ringview = nullptr;
+ vte::base::BidiRow const *bidirow = nullptr;
+ vte::grid::column_t col_vis;
if (attributes)
g_array_set_size (attributes, 0);
@@ -6116,26 +6274,50 @@ Terminal::get_text(vte::grid::row_t start_row,
if (start_col < 0)
start_col = 0;
- vte::grid::column_t next_first_column = block ? start_col : 0;
- vte::grid::column_t col = start_col;
+ if (block) {
+ /* Rectangular selection operates on the visual contents, not the logical.
+ * m_ringview corresponds to the currently onscreen bits, therefore does not
+ * necessarily include the entire selection.
+ * Modifying m_ringview and then reverting would be a bit cumbersome,
+ * creating a new one for the selection is simpler. */
+ ringview = new vte::base::RingView();
+ ringview->set_ring(m_screen->row_data);
+ ringview->set_rows(start_row, end_row - start_row + 1);
+ ringview->set_width(m_column_count);
+ ringview->maybe_update();
+ }
+
+ vte::grid::column_t col = block ? 0 : start_col;
vte::grid::row_t row;
- for (row = start_row; row < end_row + 1; row++, col = next_first_column) {
+ for (row = start_row; row < end_row + 1; row++, col = 0) {
VteRowData const* row_data = find_row_data(row);
gsize last_empty, last_nonempty;
vte::grid::column_t last_emptycol, last_nonemptycol;
- vte::grid::column_t line_last_column = (block || row == end_row) ? end_col : G_MAXLONG;
+ vte::grid::column_t line_last_column = (!block && row == end_row) ? end_col : m_column_count;
last_empty = last_nonempty = string->len;
last_emptycol = last_nonemptycol = -1;
attr.row = row;
- attr.column = col;
+ attr.column = col; // FIXME is attr.column supposed to contain logical or visual? What is it
used for?
pcell = NULL;
if (row_data != NULL) {
+ bidirow = ringview ? ringview->get_row_map(row) : nullptr;
while (col < line_last_column &&
(pcell = _vte_row_data_get (row_data, col))) {
- attr.column = col;
+ /* In block mode, we scan each row from its very beginning to its very end
in logical order,
+ * and here filter out the characters that are visually outside of the
block. */
+ if (bidirow) {
+ col_vis = bidirow->log2vis(col);
+ // FIXME handle CJK and friends consistently with cell_is_selected().
+ if (col_vis < start_col || col_vis >= end_col) {
+ col++;
+ continue;
+ }
+ }
+
+ attr.column = col; // FIXME ditto
/* If it's not part of a multi-column character,
* and passes the selection criterion, add it to
@@ -6234,6 +6416,8 @@ Terminal::get_text(vte::grid::row_t start_row,
}
}
+ delete ringview;
+
/* Sanity check. */
if (attributes != nullptr)
g_assert_cmpuint(string->len, ==, attributes->len);
@@ -6807,6 +6991,8 @@ Terminal::widget_motion_notify(GdkEventMotion *event)
{
bool handled = false;
+ ringview_maybe_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -6815,6 +7001,8 @@ Terminal::widget_motion_notify(GdkEventMotion *event)
"Motion notify %s %s\n",
pos.to_string(), rowcol.to_string());
+ ringview_maybe_update();
+
read_modifiers(base_event);
switch (event->type) {
@@ -6872,6 +7060,8 @@ Terminal::widget_button_press(GdkEventButton *event)
bool handled = false;
gboolean start_selecting = FALSE, extend_selecting = FALSE;
+ ringview_maybe_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -7021,6 +7211,8 @@ Terminal::widget_button_release(GdkEventButton *event)
{
bool handled = false;
+ ringview_maybe_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -7138,6 +7330,8 @@ Terminal::widget_focus_out(GdkEventFocus *event)
void
Terminal::widget_enter(GdkEventCrossing *event)
{
+ ringview_maybe_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
@@ -7155,6 +7349,8 @@ Terminal::widget_enter(GdkEventCrossing *event)
void
Terminal::widget_leave(GdkEventCrossing *event)
{
+ ringview_maybe_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
@@ -8827,6 +9023,27 @@ Terminal::draw_cells_with_attributes(struct _vte_draw_text_request *items,
}
+void
+Terminal::ringview_maybe_update()
+{
+ m_ringview.set_ring (m_screen->row_data);
+ /* Due to possibly unaligned height and per-pixel scrolling, up to 2 more lines than the
+ * logical height can be partially visible.
+ * If a row is just underneath the current viewport, we still need to run BiDi on it
+ * because the top an outlined rectangle cursor shape peeks in into the viewport, and we need
+ * to know where the BiDi algorithm maps that cursor. However, +2 is still enough for this
+ * as long as the outline cursor is 1px thin. If we ever make it wider, we'll need +3.
+ */
+ m_ringview.set_rows ((long) m_screen->scroll_delta, m_row_count + 2);
+ m_ringview.set_width (m_column_count);
+ m_ringview.maybe_update ();
+}
+
+/* XXX tmp hack */
+#define _vte_row_data_get_visual(row_data_p, bidimap, col) \
+ row_data_p == nullptr ? nullptr : _vte_row_data_get(row_data_p, bidimap->vis2log(col))
+
+
/* Paint the contents of a given row at the given location. Take advantage
* of multiple-draw APIs by finding runs of characters with identical
* attributes and bundling them together. */
@@ -8846,10 +9063,12 @@ Terminal::draw_rows(VteScreen *screen_,
guint fore = VTE_DEFAULT_FG, nfore, back = VTE_DEFAULT_BG, nback, deco = VTE_DEFAULT_FG, ndeco;
gboolean hyperlink = FALSE, nhyperlink, hilite = FALSE, nhilite;
gboolean selected;
+ gboolean nrtl = FALSE, rtl; /* for debugging */
uint32_t attr = 0, nattr;
guint item_count;
const VteCell *cell;
VteRowData const* row_data;
+ vte::base::BidiRow const* bidirow;
auto const column_count = m_column_count;
uint32_t const attr_mask = m_allow_bold ? ~0 : ~VTE_ATTR_BOLD_MASK;
@@ -8877,25 +9096,28 @@ Terminal::draw_rows(VteScreen *screen_,
if (row_data == nullptr)
continue; /* Skip row. FIXME: just paint this row empty? */
+ bidirow = m_ringview.get_row_map(row);
i = j = 0;
/* Walk the line.
* Locate runs of identical bg colors within a row, and paint each run as a single
rectangle. */
do {
/* Get the first cell's contents. */
- cell = row_data ? _vte_row_data_get (row_data, i) : nullptr;
+ cell = row_data ? _vte_row_data_get_visual (row_data, bidirow, i) : nullptr;
/* Find the colors for this cell. */
- selected = cell_is_selected(i, row);
+ selected = cell_is_selected_vis(i, row);
determine_colors(cell, selected, &fore, &back, &deco);
+ rtl = bidirow->vis_is_rtl(i);
while (++j < column_count) {
/* Retrieve the next cell. */
- cell = row_data ? _vte_row_data_get (row_data, j) : nullptr;
+ cell = row_data ? _vte_row_data_get_visual (row_data, bidirow, j) : nullptr;
/* Resolve attributes to colors where possible and
* compare visual attributes to the first character
* in this chunk. */
- selected = cell_is_selected(j, row);
+ selected = cell_is_selected_vis(j, row);
determine_colors(cell, selected, &nfore, &nback, &ndeco);
- if (nback != back) {
+ nrtl = bidirow->vis_is_rtl(j);
+ if (nback != back || (_vte_debug_on (VTE_DEBUG_BIDI) && nrtl != rtl)) {
break;
}
}
@@ -8909,6 +9131,21 @@ Terminal::draw_rows(VteScreen *screen_,
row_height,
&bg, VTE_DRAW_OPAQUE);
}
+ if (G_UNLIKELY (_vte_debug_on (VTE_DEBUG_BIDI) && rtl)) {
+ /* Debug: Highlight RTL letters with a slightly different background. */
+ vte::color::rgb bg;
+ rgb_from_index<8, 8, 8>(back, bg);
+ bg.red = 0xC000 + (bg.red - 0xC000) / 2;
+ bg.green = 0xC000 + (bg.green - 0xC000) / 2;
+ bg.blue = 0xC000 + (bg.blue - 0xC000) / 2;
+ _vte_draw_fill_rectangle (
+ m_draw,
+ i * column_width,
+ y + row_height / 8,
+ (j - i) * column_width,
+ row_height * 3 / 4,
+ &bg, VTE_DRAW_OPAQUE);
+ }
/* We'll need to continue at the first cell which didn't
* match the first one in this set. */
i = j;
@@ -8939,15 +9176,18 @@ Terminal::draw_rows(VteScreen *screen_,
/* Ensure that drawing is restricted to the cell (plus the overdraw area) */
_vte_draw_autoclip_t clipper{m_draw, &rect};
+ bidirow = m_ringview.get_row_map(row);
+
/* Walk the line.
* Locate runs of identical attributes within a row, and draw each run using a single
draw_cells() call. */
item_count = 0;
for (col = 0; col < column_count; ) {
/* Get the character cell's contents. */
- cell = _vte_row_data_get (row_data, col);
+ cell = _vte_row_data_get_visual (row_data, bidirow, col);
if (cell == NULL) {
- /* There'll be no more real cells in this row. */
- break;
+ /* We're rendering BiDi text in visual order, so an unused cell can be
followed by a used one. */
+ col++;
+ continue;
}
nhyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0);
@@ -8966,11 +9206,11 @@ Terminal::draw_rows(VteScreen *screen_,
/* Find the colors for this cell. */
nattr = cell->attr.attr;
- selected = cell_is_selected(col, row);
+ selected = cell_is_selected_vis(col, row);
determine_colors(cell, selected, &nfore, &nback, &ndeco);
nhilite = (nhyperlink && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) ||
- (!nhyperlink && m_match != nullptr && m_match_span.contains(row, col));
+ (!nhyperlink && m_match != nullptr && m_match_span.contains(row,
bidirow->vis2log(col)));
/* See if it no longer fits the run. */
if (item_count > 0 &&
@@ -9025,10 +9265,12 @@ Terminal::draw_rows(VteScreen *screen_,
hilite = nhilite;
g_assert_cmpint (item_count, <, column_count);
- items[item_count].c = c;
+ items[item_count].c = bidirow->vis_get_shaped_char(col, c);
items[item_count].columns = j - col;
- items[item_count].x = col * column_width;
+ items[item_count].x = (col - (bidirow->vis_is_rtl(col) ? j - col - 1 : 0)) *
column_width;
items[item_count].y = y;
+ items[item_count].mirror = bidirow->vis_is_rtl(col);
+ items[item_count].box_mirror = !!(row_data->attr.bidi_flags & VTE_BIDI_BOX_MIRROR);
item_count++;
g_assert_cmpint (j, >, col);
@@ -9051,7 +9293,7 @@ Terminal::paint_cursor()
{
struct _vte_draw_text_request item;
vte::grid::row_t drow;
- vte::grid::column_t col;
+ vte::grid::column_t col, viscol;
int width, height, cursor_width;
guint style = 0;
guint fore, back, deco;
@@ -9084,6 +9326,9 @@ Terminal::paint_cursor()
/* Find the first cell of the character "under" the cursor.
* This is for CJK. For TAB, paint the cursor where it really is. */
+ VteRowData const *row_data = find_row_data(drow);
+ vte::base::BidiRow const *bidirow = m_ringview.get_row_map(drow);
+
auto cell = find_charcell(col, drow);
while (cell != NULL && cell->attr.fragment() && cell->c != '\t' && col > 0) {
col--;
@@ -9091,15 +9336,18 @@ Terminal::paint_cursor()
}
/* Draw the cursor. */
- item.c = (cell && cell->c) ? cell->c : ' ';
+ viscol = bidirow->log2vis(col);
+ item.c = (cell && cell->c) ? bidirow->vis_get_shaped_char(viscol, cell->c) : ' ';
item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns() : 1;
- item.x = col * width;
+ item.x = (viscol - ((cell && bidirow->vis_is_rtl(viscol)) ? cell->attr.columns() - 1 : 0)) * width;
item.y = row_to_pixel(drow);
+ item.mirror = bidirow->vis_is_rtl(viscol);
+ item.box_mirror = (row_data && (row_data->attr.bidi_flags & VTE_BIDI_BOX_MIRROR));
if (cell && cell->c != 0) {
style = _vte_draw_get_style(cell->attr.bold(), cell->attr.italic());
}
- selected = cell_is_selected(col, drow);
+ selected = cell_is_selected_vis(viscol, drow);
determine_cursor_colors(cell, selected, &fore, &back, &deco);
rgb_from_index<8, 8, 8>(back, bg);
@@ -9121,9 +9369,23 @@ Terminal::paint_cursor()
stem_width = (int) (((float) (m_char_ascent + m_char_descent)) *
m_cursor_aspect_ratio + 0.5);
stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, m_cell_width);
+ // FIXME make an exception when the cursor is just after the last character:
+ // show it next to the last character (wherever it appears due to BiDi) rather than
+ // beyond the end of the string, as KDE/Qt does. Maybe not just for I-beam.
+
+ if (row_data && bidirow->vis_is_rtl(viscol))
+ x += item.columns * m_cell_width - stem_width;
+
_vte_draw_fill_rectangle(m_draw,
x, y + m_char_padding.top, stem_width, m_char_ascent +
m_char_descent,
&bg, VTE_DRAW_OPAQUE);
+
+ if (focus && row_data && bidirow->has_foreign())
+ _vte_draw_fill_rectangle(m_draw,
+ bidirow->vis_is_rtl(viscol) ? x - stem_width : x +
stem_width, y + m_char_padding.top,
+ stem_width, stem_width,
+ &bg, VTE_DRAW_OPAQUE);
+
break;
}
@@ -9207,12 +9469,19 @@ void
Terminal::paint_im_preedit_string()
{
int col, columns;
+ long row;
long width, height;
int i, len;
if (m_im_preedit.empty())
return;
+ /* Get the row's BiDi information. */
+ row = m_screen->cursor.row;
+ if (row < first_displayed_row() || row > last_displayed_row())
+ return;
+ vte::base::BidiRow const *bidirow = m_ringview.get_row_map(row);
+
/* Keep local copies of rendering information. */
width = m_cell_width;
height = m_cell_height;
@@ -9223,7 +9492,7 @@ Terminal::paint_im_preedit_string()
/* If the pre-edit string won't fit on the screen if we start
* drawing it at the cursor's position, move it left. */
- col = m_screen->cursor.col;
+ col = bidirow->log2vis(m_screen->cursor.col);
if (col + columns > m_column_count) {
col = MAX(0, m_column_count - columns);
}
@@ -9234,7 +9503,7 @@ Terminal::paint_im_preedit_string()
const char *preedit = m_im_preedit.c_str();
int preedit_cursor;
- items = g_new(struct _vte_draw_text_request, len);
+ items = g_new0(struct _vte_draw_text_request, len);
for (i = columns = 0; i < len; i++) {
items[i].c = g_utf8_get_char(preedit);
items[i].columns = _vte_unichar_width(items[i].c,
@@ -9301,6 +9570,8 @@ Terminal::widget_draw(cairo_t *cr)
if (region == NULL)
return;
+ ringview_maybe_update();
+
allocated_width = get_allocated_width();
allocated_height = get_allocated_height();
@@ -9433,7 +9704,8 @@ Terminal::widget_scroll(GdkEventScroll *event)
int button;
GdkEvent *base_event = reinterpret_cast<GdkEvent*>(event);
- auto rowcol = confined_grid_coords_from_event(base_event);
+
+ ringview_maybe_update();
read_modifiers(base_event);
@@ -9468,6 +9740,8 @@ Terminal::widget_scroll(GdkEventScroll *event)
"Scroll application by %d lines, smooth scroll delta set back to %f\n",
cnt, m_mouse_smooth_scroll_delta);
+ auto rowcol = confined_grid_coords_from_event(base_event);
+
button = cnt > 0 ? 5 : 4;
if (cnt < 0)
cnt = -cnt;
@@ -9946,6 +10220,8 @@ Terminal::reset(bool clear_tabstops,
/* Reset the saved cursor. */
save_cursor(&m_normal_screen);
save_cursor(&m_alternate_screen);
+ /* BiDi */
+ m_bidi_rtl = FALSE;
/* Cause everything to be redrawn (or cleared). */
invalidate_all();
@@ -10310,8 +10586,13 @@ Terminal::process(bool emit_adj_changed)
process_incoming();
}
m_input_bytes = 0;
- } else
+ } else {
+ // FIXMEegmont why is it needed here, and is this the best place?
+ // Without this, sudden two-finger kinetic scrolls result in crash.
+ ringview_maybe_update();
+
emit_pending_signals();
+ }
return is_active;
}
diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h
index 26ac236e..4ca6efb4 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -113,7 +113,7 @@ struct _VteTerminalClass {
/* The structure we return as the supplemental attributes for strings. */
struct _VteCharAttributes {
/*< private >*/
- long row, column;
+ long row, column; // FIXME clarify if column is logical or visual
PangoColor fore, back;
guint underline:1, strikethrough:1, columns:4;
};
diff --git a/src/vtedefines.hh b/src/vtedefines.hh
index 71896a76..3502fec1 100644
--- a/src/vtedefines.hh
+++ b/src/vtedefines.hh
@@ -136,3 +136,6 @@
/* Max depth of title stack */
#define VTE_WINDOW_TITLE_STACK_MAX_DEPTH (8)
+
+/* Maximum length of a paragraph, in lines, that might get proper BiDi treatment. */
+#define VTE_BIDI_PARAGRAPH_LENGTH_MAX 500
diff --git a/src/vtedraw.cc b/src/vtedraw.cc
index 215e27ee..aafb012b 100644
--- a/src/vtedraw.cc
+++ b/src/vtedraw.cc
@@ -26,6 +26,7 @@
#include <glib.h>
#include <gtk/gtk.h>
+#include "bidi.hh"
#include "vtedraw.hh"
#include "vtedefines.hh"
#include "debug.h"
@@ -1487,6 +1488,12 @@ _vte_draw_text_internal (struct _vte_draw *draw,
for (i = 0; i < n_requests; i++) {
vteunistr c = requests[i].c;
+
+ if (G_UNLIKELY (requests[i].mirror)) {
+ // FIXME what if 'c' is actually a real vteunistr?
+ vte_bidi_get_mirror_char (c, requests[i].box_mirror, &c);
+ }
+
struct unistr_info *uinfo = font_info_get_unistr_info (font, c);
union unistr_font_info *ufi = &uinfo->ufi;
int x, y;
diff --git a/src/vtedraw.hh b/src/vtedraw.hh
index 7352d376..7006b77d 100644
--- a/src/vtedraw.hh
+++ b/src/vtedraw.hh
@@ -44,6 +44,8 @@ struct _vte_draw;
struct _vte_draw_text_request {
vteunistr c;
gshort x, y, columns;
+ guint8 mirror : 1;
+ guint8 box_mirror : 1;
};
guint _vte_draw_get_style(gboolean bold, gboolean italic);
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index f8f296b9..262691bd 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -1791,6 +1791,12 @@ vte_get_features (void)
"+GNUTLS"
#else
"-GNUTLS"
+#endif
+ " "
+#ifdef WITH_FRIBIDI
+ "+BIDI"
+#else
+ "-BIDI"
#endif
;
}
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 7d4815e0..8193610b 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -24,6 +24,7 @@
#include "vtetypes.hh"
#include "vtedraw.hh"
#include "reaper.hh"
+#include "bidi.hh"
#include "ring.hh"
#include "buffer.h"
#include "parser.hh"
@@ -86,6 +87,14 @@ enum {
VTE_SGR_COLOR_SPEC_LEGACY = 5
};
+enum {
+ VTE_BIDI_IMPLICIT = 1 << 0,
+ VTE_BIDI_RTL = 1 << 1,
+ VTE_BIDI_AUTO = 1 << 2,
+ VTE_BIDI_BOX_MIRROR = 1 << 3,
+ VTE_BIDI_ALL = (1 << 4) - 1,
+};
+
struct vte_regex_and_flags {
VteRegex *regex;
guint32 match_flags;
@@ -392,7 +401,7 @@ public:
gboolean m_selecting_had_delta;
gboolean m_selection_block_mode; // FIXMEegmont move it into a 4th value in vte_selection_type?
enum vte_selection_type m_selection_type;
- vte::grid::halfcoords m_selection_origin, m_selection_last;
+ vte::grid::halfcoords m_selection_origin, m_selection_last; /* BiDi: logical in normal modes,
visual in m_selection_block_mode */
vte::grid::span m_selection_resolved;
/* Clipboard data information. */
@@ -608,6 +617,10 @@ public:
const char *m_hyperlink_hover_uri; /* data is owned by the ring */
long m_hyperlink_auto_id;
+ /* BiDi parameters outside of ECMA and DEC private modes */
+ guint m_bidi_rtl : 1;
+ vte::base::RingView m_ringview;
+
public:
// FIXMEchpe inline!
@@ -660,6 +673,10 @@ public:
void invalidate_match_span();
void invalidate_all();
+ guint8 get_bidi_flags();
+ void apply_bidi_attributes(vte::grid::row_t row, guint8 bidi_flags, guint8 bidi_flags_mask);
+ void maybe_apply_bidi_attributes(guint8 bidi_flags_mask);
+
void reset_update_rects();
bool invalidate_dirty_rects_and_process_updates();
void time_process_incoming();
@@ -713,6 +730,7 @@ public:
inline bool grid_coords_in_scrollback(vte::grid::coords const& rowcol) const { return rowcol.row() <
m_screen->insert_delta; }
+ vte::grid::row_t confine_grid_row(vte::grid::row_t const& row) const;
vte::grid::coords confine_grid_coords(vte::grid::coords const& rowcol) const;
vte::grid::coords confined_grid_coords_from_event(GdkEvent const* event) const;
vte::grid::coords confined_grid_coords_from_view_coords(vte::view::coords const& pos) const;
@@ -936,8 +954,8 @@ public:
void resolve_selection();
void selection_maybe_swap_endpoints(vte::view::coords const& pos);
void modify_selection(vte::view::coords const& pos);
- bool cell_is_selected(vte::grid::column_t col,
- vte::grid::row_t) const;
+ bool cell_is_selected_vis(vte::grid::column_t col,
+ vte::grid::row_t) const;
void reset_default_attributes(bool reset_hyperlink);
@@ -1313,6 +1331,8 @@ public:
vte::parser::StringTokeniser::const_iterator& token,
vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+ void ringview_maybe_update();
+
/* Sequence handlers */
bool m_line_wrapped; // signals line wrapped from character insertion
// Note: inlining the handlers seems to worsen the performance, so we don't do that
diff --git a/src/vterowdata.cc b/src/vterowdata.cc
index 88edba1a..cef1858f 100644
--- a/src/vterowdata.cc
+++ b/src/vterowdata.cc
@@ -82,7 +82,10 @@ _vte_cells_free (VteCells *cells)
void
_vte_row_data_init (VteRowData *row)
{
+ // FIXME pass the bidi attrs to this method?
+ guint8 bidi_flags_save = row->attr.bidi_flags;
memset (row, 0, sizeof (*row));
+ row->attr.bidi_flags = bidi_flags_save;
}
void
diff --git a/src/vterowdata.hh b/src/vterowdata.hh
index 9d4b2767..12cbf311 100644
--- a/src/vterowdata.hh
+++ b/src/vterowdata.hh
@@ -36,7 +36,8 @@ G_BEGIN_DECLS
*/
typedef struct _VteRowAttr {
- guint8 soft_wrapped: 1;
+ guint8 soft_wrapped : 1;
+ guint8 bidi_flags : 4;
} VteRowAttr;
static_assert(sizeof (VteRowAttr) == 1, "VteRowAttr has wrong size");
diff --git a/src/vteseq.cc b/src/vteseq.cc
index d12ee329..59387676 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -315,6 +315,7 @@ Terminal::clear_current_line()
/* Add enough cells to the end of the line to fill out the row. */
_vte_row_data_fill (rowdata, &m_fill_defaults, m_column_count);
set_hard_wrapped(m_screen->cursor.row);
+ rowdata->attr.bidi_flags = get_bidi_flags();
/* Repaint this row. */
invalidate_row(m_screen->cursor.row);
}
@@ -342,6 +343,7 @@ Terminal::clear_above_current()
/* Add new cells until we fill the row. */
_vte_row_data_fill (rowdata, &m_fill_defaults, m_column_count);
set_hard_wrapped(i);
+ rowdata->attr.bidi_flags = get_bidi_flags();
/* Repaint the row. */
invalidate_row(i);
}
@@ -469,6 +471,13 @@ Terminal::set_mode_ecma(vte::parser::Sequence const& seq,
continue;
m_modes_ecma.set(mode, set);
+
+ if (mode == m_modes_ecma.eBDSM) {
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "BiDi %s mode\n",
+ set ? "implicit" : "explicit");
+ maybe_apply_bidi_attributes(VTE_BIDI_IMPLICIT);
+ }
}
}
@@ -597,6 +606,20 @@ Terminal::set_mode_private(int mode,
feed_focus_event_initial();
break;
+ case vte::terminal::modes::Private::eVTE_BOX_DRAWING_MIRROR:
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "BiDi box drawing mirroring %s\n",
+ set ? "enabled" : "disabled");
+ maybe_apply_bidi_attributes(VTE_BIDI_BOX_MIRROR);
+ break;
+
+ case vte::terminal::modes::Private::eVTE_BIDI_AUTO:
+ _vte_debug_print(VTE_DEBUG_BIDI,
+ "BiDi dir autodetection %s\n",
+ set ? "enabled" : "disabled");
+ maybe_apply_bidi_attributes(VTE_BIDI_AUTO);
+ break;
+
default:
break;
}
@@ -745,6 +768,8 @@ Terminal::clear_below_current()
_vte_row_data_fill(rowdata, &m_fill_defaults, m_column_count);
}
set_hard_wrapped(i);
+ if (i > m_screen->cursor.row)
+ rowdata->attr.bidi_flags = get_bidi_flags();
/* Repaint this row. */
invalidate_row(i);
}
@@ -1023,6 +1048,7 @@ Terminal::line_feed()
{
ensure_cursor_is_onscreen();
cursor_down(true);
+ maybe_apply_bidi_attributes(VTE_BIDI_ALL);
}
void
@@ -4746,7 +4772,7 @@ Terminal::DECSTBM(vte::parser::Sequence const& seq)
} else {
/* Maybe extend the ring -- bug 710483 */
while (_vte_ring_next(m_screen->row_data) < m_screen->insert_delta + m_row_count)
- _vte_ring_insert(m_screen->row_data, _vte_ring_next(m_screen->row_data));
+ _vte_ring_insert(m_screen->row_data, _vte_ring_next(m_screen->row_data),
get_bidi_flags());
}
home_cursor();
@@ -6918,7 +6944,27 @@ Terminal::SCP(vte::parser::Sequence const& seq)
* args[1]: no default
*
* References: ECMA-48 § 8.3.111
+ * [FIXME link to our spec]
*/
+
+ auto const param = seq.collect1(0);
+ switch (param) {
+ case 1:
+ m_bidi_rtl = FALSE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: switch to LTR\n");
+ break;
+ case 2:
+ m_bidi_rtl = TRUE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: switch to RTL\n");
+ break;
+ default:
+ /* FIXME switch to the emulator's default */
+ m_bidi_rtl = FALSE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: default direction restored\n");
+ break;
+ }
+
+ maybe_apply_bidi_attributes(VTE_BIDI_RTL);
}
void
@@ -7386,7 +7432,30 @@ Terminal::SPD(vte::parser::Sequence const& seq)
* args[1]: 0
*
* References: ECMA-48 § 8.3.130
+ * [FIXME link to our spec]
*/
+
+ auto const param = seq.collect1(0);
+ switch (param) {
+ case -1:
+ case 0:
+ m_bidi_rtl = FALSE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: switch to LTR\n");
+ break;
+ case 3:
+ m_bidi_rtl = TRUE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: switch to RTL\n");
+ break;
+ default:
+ /* FIXME switch to the emulator's default */
+ m_bidi_rtl = FALSE;
+ _vte_debug_print(VTE_DEBUG_BIDI, "BiDi: default direction restored\n");
+ break;
+ }
+
+ maybe_apply_bidi_attributes(VTE_BIDI_RTL);
+
+ // FIXME apply to all the onscreen lines?
}
void
diff --git a/src/vteunistr.cc b/src/vteunistr.cc
index 69c47fe0..319bd6bf 100644
--- a/src/vteunistr.cc
+++ b/src/vteunistr.cc
@@ -162,6 +162,39 @@ _vte_unistr_get_base (vteunistr s)
return (gunichar) s;
}
+void
+_vte_unistr_append_to_gunichars (vteunistr s, GArray *a)
+{
+ if (G_UNLIKELY (s >= VTE_UNISTR_START)) {
+ struct VteUnistrDecomp *decomp;
+ decomp = &DECOMP_FROM_UNISTR (s);
+ _vte_unistr_append_to_gunichars (decomp->prefix, a);
+ s = decomp->suffix;
+ }
+ gunichar val = (gunichar) s;
+ g_array_append_val (a, val);
+}
+
+vteunistr
+_vte_unistr_replace_base (vteunistr s, gunichar c)
+{
+ g_return_val_if_fail (s < unistr_next, s);
+
+ if (G_LIKELY (_vte_unistr_get_base(s) == c))
+ return s;
+
+ GArray *a = g_array_new (FALSE, FALSE, sizeof (gunichar));
+ _vte_unistr_append_to_gunichars (s, a);
+ g_assert_cmpint(a->len, >=, 1);
+
+ s = c;
+ for (glong i = 1; i < a->len; i++)
+ s = _vte_unistr_append_unichar (s, g_array_index (a, gunichar, i));
+
+ g_array_free (a, TRUE);
+ return s;
+}
+
void
_vte_unistr_append_to_string (vteunistr s, GString *gs)
{
diff --git a/src/vteunistr.h b/src/vteunistr.h
index 9bde6b2c..84cb0562 100644
--- a/src/vteunistr.h
+++ b/src/vteunistr.h
@@ -73,6 +73,19 @@ _vte_unistr_append_unistr (vteunistr s, vteunistr t);
gunichar
_vte_unistr_get_base (vteunistr s);
+/**
+ * _vte_unistr_append_to_string:
+ * @s: a #vteunistr
+ * @c: Unicode character to replace the base character of @s.
+ *
+ * Creates a vteunistr value where the base character from @s is
+ * replaced by @c, while the combining characters from @s are carried over.
+ *
+ * Returns: the new #vteunistr value
+ */
+vteunistr
+_vte_unistr_replace_base (vteunistr s, gunichar c);
+
/**
* _vte_unistr_append_to_string:
* @s: a #vteunistr
@@ -84,6 +97,16 @@ _vte_unistr_get_base (vteunistr s);
void
_vte_unistr_append_to_string (vteunistr s, GString *gs);
+/**
+ * _vte_unistr_append_to_gunichars:
+ * @s: a #vteunistr
+ * @a: a #GArray of #gunichar items to append @s to
+ *
+ * Appends @s to @a.
+ **/
+void
+_vte_unistr_append_to_gunichars (vteunistr s, GArray *a);
+
/**
* _vte_unistr_strlen:
* @s: a #vteunistr
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]