12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760 |
- // Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
- //
- // This program is free software; you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation; either version 2 of the License, or
- // (at your option) any later version.
- //
- // This program 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 General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program; if not, write to the Free Software
- // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
- #include <config.h>
- #include "editbox.h"
- #include "transtbl.h"
- #include "univalues.h"
- #include "mk_wcwidth.h"
- #include "my_wctob.h"
- #include "shaping.h"
- #include "themes.h"
- #include "dbg.h"
- // Default representations for some characters and concepts.
- // If you want to change these, don't edit this file; edit "reprtab" instead.
- #define FAILED_CONV_REPR '?'
- #define CONTROL_REPR '^'
- #define NSM_REPR '\''
- #define WIDE_REPR 'A'
- #define TRIM_REPR '$'
- #define WRAP_RTL_REPR '/'
- #define WRAP_LTR_REPR '\\'
- #define EOP_UNICODE_REPR 0xB6
- #define EOP_DOS_REPR 0xB5
- #define EOP_UNIX_LTR_REPR 0xAB
- #define EOP_UNIX_RTL_REPR 0xBB
- #define EOP_MAC_REPR '@'
- #define EOP_NONE_REPR 0xAC
- static bool do_mirror = true; // Do mirroring also when !bidi_enabled ?
- // Setters and Togglers for various display options {{{
- void EditBox::set_formatting_marks(bool value)
- {
- show_paragraph_endings = value;
- show_explicits = value;
- show_tabs = value;
- rewrap_all(); // width changes because we show/hide explicit marks
- NOTIFY_CHANGE(formatting_marks);
- }
- // toggle_formatting_marks() - interactive command to toggle display of
- // formatting marks.
- INTERACTIVE void EditBox::toggle_formatting_marks()
- {
- set_formatting_marks(!has_formatting_marks());
- }
- void EditBox::set_maqaf_display(maqaf_display_t disp)
- {
- maqaf_display = disp;
- request_update(rgnAll);
- NOTIFY_CHANGE(maqaf);
- }
- // toggle_maqaf() - interactive command to toggle the display of maqaf.
- INTERACTIVE void EditBox::toggle_maqaf()
- {
- switch (maqaf_display) {
- case mqfAsis: set_maqaf_display(mqfTransliterated); break;
- case mqfTransliterated: set_maqaf_display(mqfHighlighted); break;
- case mqfHighlighted: set_maqaf_display(mqfAsis); break;
- }
- }
- bool EditBox::set_rtl_nsm_display(rtl_nsm_display_t disp)
- {
- if (disp == rtlnsmAsis && (!terminal::is_utf8 || terminal::is_fixed)) {
- NOTIFY_ERROR(cant_display_nsm);
- return false;
- }
- rtl_nsm_display = disp;
- rewrap_all();
- NOTIFY_CHANGE(rtl_nsm);
- return true;
- }
- // toggle_rtl_nsm() - interactive command to toggle display of Hebrew
- // points.
- INTERACTIVE void EditBox::toggle_rtl_nsm()
- {
- switch (rtl_nsm_display) {
- case rtlnsmOff:
- set_rtl_nsm_display(rtlnsmTransliterated);
- break;
- case rtlnsmTransliterated:
- if (set_rtl_nsm_display(rtlnsmAsis))
- break;
- // fall-through
- case rtlnsmAsis:
- set_rtl_nsm_display(rtlnsmOff);
- break;
- }
- }
- void EditBox::enable_bidi(bool value)
- {
- bidi_enabled = value;
- cache.invalidate();
- invalidate_optimal_vis_column();
- request_update(rgnAll);
- }
- INTERACTIVE void EditBox::toggle_bidi()
- {
- enable_bidi(!is_bidi_enabled());
- }
- // }}}
- // Updating {{{
- // request_update() - request that some region be repainted next time the
- // update() method is called.
- void EditBox::request_update(region rgn)
- {
- update_region |= rgn;
- }
- // request_update(from, to) - similar to request_update(rgnAll), but only
- // paints the paragraphs in the range [from, to].
- void EditBox::request_update(int from, int to)
- {
- if (update_region & rgnRange) {
- // if there's already a pending rgnRange request,
- // give up and paint the whole window.
- update_region = rgnAll;
- } else {
- update_region |= rgnRange;
- update_from = from;
- update_to = to;
- }
- }
- void EditBox::update()
- {
- if (update_region == rgnNone)
- return;
- static int last_cursor_para = -1;
- if (is_primary_mark_set()) {
- // Determining which paragraphs to repaint when a selection is active
- // is a bit complicated, so we revert to a simple decision: if the
- // cursor hasn't moved from the paragraph we painted in the previous
- // update then the selection hasn't moved to include new paragraphs
- // and we can paint only this paragraph; else repaint the whole
- // window.
- if (last_cursor_para == cursor.para) {
- // we repaint the whole paragraph even if we're asked to only
- // reposition the cursor, because the selection has changed.
- update_region |= rgnCurrent;
- } else {
- update_region = rgnAll;
- }
- last_cursor_para = cursor.para;
- }
- if (wrap_type == wrpOff && update_region == rgnCursor) {
- // Well... this is a flaw in our update mechanism. In no-wrap mode we
- // repaint the whole paragraph even if we're asked to only reposition
- // the cursor. that's because the new cursor position might be in a
- // segment which is not shown on screen.
- update_region = rgnCurrent;
- }
-
- if (update_region & rgnAll) {
- wbkgd(wnd, get_attr(EDIT_ATTR));
- werase(wnd);
- // we invalidate the cache here instead of doing it after every change
- // that affects the display.
- cache.invalidate();
- }
- int window_line = -top_line.inner_line;
- int curr_para_line = 0; // the window line at which the current para starts.
- // "=0" to silence the compiler.
- for (int i = top_line.para;
- i < parags_count()
- && window_line < window_height();
- i++) {
- Paragraph *para = paragraphs[i];
- if (para == curr_para()) {
- // we paint the current para outside of the loop because its
- // painting also positions the cursor (which subsequent draws
- // will invalidate).
- curr_para_line = window_line;
- if (update_region != rgnCursor) {
- // we don't paint it yet, but we erase its background
- for (int k = window_line;
- k < window_line + para->breaks_count()
- && k < window_height();
- k++) {
- wmove(wnd, k, 0);
- wclrtoeol(wnd);
- }
- }
- }
- else if ((update_region & rgnAll)
- || ((update_region & rgnRange)
- && i >= update_from && i <= update_to)) {
- redraw_paragraph(*para, window_line, false, i);
- }
- window_line += para->breaks_count();
- }
- // paint the current paragraph
- bool only_cursor = (update_region == rgnCursor);
- redraw_paragraph(*curr_para(), curr_para_line, only_cursor, cursor.para);
- wnoutrefresh(wnd);
- update_region = rgnNone;
- }
- // }}}
- // BiDi Reordering {{{
- template <class VAL, class IDX>
- inline void reverse(VAL *array, IDX len)
- {
- if (len == 0)
- return; // IDX may be unsigned, so "-1" won't work.
- for (IDX start = 0, end = len - 1; start < end; start++, end--) {
- VAL tmp = array[start];
- array[start] = array[end];
- array[end] = tmp;
- }
- }
- // reorder() - Reorder Resolved Levels. Rules L2..L4. Unfortunately, FriBiDi
- // has a flawed interface: this should be done after line-wrapping, and
- // FriBiDi doesn't have a separate function for reordering, so we have to do
- // it ourselves. (but it isn't so bad: that way we are not dependent on the
- // BiDi engine, because we use very little of it.)
- static void reorder(level_t *levels, idx_t len,
- idx_t *position_V_to_L, idx_t *position_L_to_V,
- unichar *str,
- attribute_t *attributes = NULL,
- bool mirror = false,
- bool reorder_nsm = false)
- {
- // We update a V_to_L vector from a L_to_V one. If the user
- // doesn't provide a L_to_V vector, we have to allocate it
- // ourselves.
- EditBox::IdxArray internal_position_V_to_L;
- if (position_L_to_V && !position_V_to_L) {
- internal_position_V_to_L.resize(len);
- position_V_to_L = internal_position_V_to_L.begin();
- }
-
- if (position_V_to_L) {
- for (idx_t i = 0; i < len; i++)
- position_V_to_L[i] = i;
- }
- level_t highest_level = 0;
- level_t lowest_odd_level = 63;
- for (idx_t i = 0; i < len; i++) {
- level_t level = levels[i];
- if (level > highest_level)
- highest_level = level;
- if ((level & 1) && level < lowest_odd_level)
- lowest_odd_level = level;
- }
- // L2
- for (level_t level = highest_level; level >= lowest_odd_level; level--)
- for (idx_t i = 0; i < len; i++)
- if (levels[i] >= level) {
- idx_t start = i;
- while (i < len && levels[i] >= level)
- i++;
- if (position_V_to_L)
- reverse(position_V_to_L + start, i - start);
- reverse(levels + start, i - start);
- if (str)
- reverse(str + start, i - start);
- if (attributes)
- reverse(attributes + start, i - start);
- }
-
- // L3, L4: Mirroring and NSM reordering.
- if (str && (mirror || reorder_nsm)) {
- for (idx_t i = 0; i < len; i++) {
- if (levels[i] & 1) {
- BiDi::mirror_char(&str[i]);
- // we don't unconditionally reorder NSMs bacause when the
- // user prefers ASCII transliteration they should be left
- // to the left of the character.
- if (reorder_nsm
- && BiDi::is_nsm(str[i]))
- {
- idx_t start = i++;
- while (i < len && BiDi::is_nsm(str[i]))
- i++;
- if (i < len) // :FIXME: only if on the same level.
- i++; // include the base character.
- // should I reorder position_V_to_L and attributes too?
- //if (position_V_to_L)
- // reverse(position_V_to_L + start, i - start);
- reverse(str + start, i - start);
- //if (attributes)
- // reverse(attributes + start, i - start);
- if (i < len)
- i--;
- }
- }
- }
- }
- if (position_L_to_V) {
- for (idx_t i = 0; i < len; i++)
- position_L_to_V[position_V_to_L[i]] = i;
- }
- }
- // }}}
- // Wrap / character traits {{{
- // most of the characters will probably be "simple" ones, meaning
- // they occupy one terminal column. IS_SIMPLE_ONE_COLUMN_CHAR helps
- // us avoid a call to wcwidth().
- #define IS_SIMPLE_ONE_COLUMN_CHAR(ch) \
- ((ch >= 32 && ch <= 126) \
- || (ch >= UNI_HEB_ALEF && ch <= UNI_HEB_TAV))
- // get_char_width() - returns the width, in terminal columns, of a character.
- // it takes into account the user's preferences (e.g. whether to show explicit
- // marks), LAM-ALEF ligature, and the terminal capabilities (e.g. whether it's
- // capable of displaying wide and combining characters). it also calculates
- // the width of a TAB character based on its position in the line (pos).
- int EditBox::get_char_width(unichar ch, int pos, wdstate *stt, bool visual)
- {
- #define LAM_N 0x0644
- #define IS_LAM_N(c) ((c) == LAM_N)
- #define ALEF_MADDA_N 0x0622
- #define ALEF_HAMZA_ABOVE_N 0x0623
- #define ALEF_HAMZA_BELOW_N 0x0625
- #define ALEF_N 0x0627
- #define IS_ALEF_N(c) ((c) == ALEF_MADDA_N \
- || (c) == ALEF_HAMZA_ABOVE_N \
- || (c) == ALEF_HAMZA_BELOW_N \
- || (c) == ALEF_N)
- // optimization
- if (IS_SIMPLE_ONE_COLUMN_CHAR(ch) && !(stt && stt->may_start_lam_alef))
- return 1;
-
- int width = mk_wcwidth(ch);
- // handle ALEF-LAM ligature
- if (terminal::do_arabic_shaping && stt) {
- if (visual ? IS_ALEF_N(ch) : IS_LAM_N(ch)) {
- stt->may_start_lam_alef = true;
- } else {
- if (stt->may_start_lam_alef
- && (visual ? IS_LAM_N(ch) : IS_ALEF_N(ch))) {
- // yes, this is the second character of the ligature.
- // return 0 (the ligature width is 1, and the first
- // character already returned 1).
- stt->may_start_lam_alef = false;
- return 0;
- } else {
- if (width != 0 || !is_shaping_transparent(ch))
- stt->may_start_lam_alef = false;
- }
- }
- }
- if (width != 1) {
- if (ch == '\t') {
- return (tab_width - (pos % tab_width));
- } else if (width == -1 || ch == '\0') {
- // We print control characters and nulls on one column, so
- // we set their width to 1.
- width = 1;
- } else if (width == 0) {
- // Calculate required width for BIDI codes and Hebrew points
- // based on user's preferences.
- if (BiDi::is_explicit_mark(ch)) {
- if (show_explicits)
- width = 1;
- } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
- if (rtl_nsm_display == rtlnsmTransliterated)
- width = 1;
- } else {
- // Now handle general NSMs:
- // if it's not a UTF-8 terminal, assume it can't combine
- // characters. Same for fixed terminals.
- if (!terminal::is_utf8 || terminal::is_fixed)
- width = 1;
- }
- } else {
- // width == 2, Asian ideographs
- if (!terminal::is_utf8 || terminal::is_fixed)
- width = 1;
- }
- }
- return width;
- }
- int EditBox::get_str_width(const unichar *str, idx_t len, bool visual)
- {
- wdstate stt;
- int width = 0;
- while (len--)
- width += get_char_width(*str++, width, &stt, true);
- return width;
- }
- // get_rev_str_width() - sums the widths of the characters starting at the
- // end of the string. it makes a difference only for the TAB character.
- int EditBox::get_rev_str_width(const unichar *str, idx_t len)
- {
- wdstate stt;
- int width = 0;
- str += len;
- while (len--)
- width += get_char_width(*(--str), width, &stt);
- return width;
- }
- // calc_vis_column() - returns the visual column the cursor is at.
- // It does a logical-to-visual conversion, then it sums, in visual order,
- // the widths of the characters till the cursor.
- int EditBox::calc_vis_column()
- {
- Paragraph &p = *curr_para();
- // Find the start and the end of the screen line.
- int inner_line = calc_inner_line();
- idx_t start = (inner_line > 0)
- ? p.line_breaks[inner_line - 1]
- : 0;
- idx_t end = p.line_breaks[inner_line];
- idx_t line_len = end - start;
- LevelsArray &levels = cache.levels;
- IdxArray &position_L_to_V = cache.position_L_to_V;
- unistring &vis = cache.vis;
- // We apply the BiDi algorithm if the
- // results are not already cached.
- if (!cache.owned_by(cursor.para)) {
- cache.invalidate(); // it's owned by someone else
- levels.resize(p.str.len());
- DBG(100, ("get_embedding_levels() - by calc_vis_column()\n"));
- BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
- p.base_dir(), levels.begin(),
- p.breaks_count(), p.line_breaks.begin(),
- !bidi_enabled);
- vis = p.str;
- position_L_to_V.resize(p.str.len());
- // reorder just the screen line.
- reorder(levels.begin() + start, line_len,
- NULL, position_L_to_V.begin() + start, vis.begin() + start);
- }
- int cursor_log_line_pos = cursor.pos - start;
- // Note that we use the term "width" to make it clear that we
- // sum the widths of the characters. "column", in the name of
- // this method, refers to the terminal column.
- int cursor_vis_width = 0;
-
- if (cursor_log_line_pos >= line_len) {
- // cursor stands at end of line. calculate the
- // width of the whole line.
- if (p.is_rtl())
- cursor_vis_width = get_rev_str_width(vis.begin() + start,
- line_len);
- else
- cursor_vis_width = get_str_width(vis.begin() + start,
- line_len);
- }
- else {
- // The cursor is inside the line; find the visual position of the
- // cursor, then calculate the width of the segment.
- int cursor_vis_line_pos = position_L_to_V[start + cursor_log_line_pos];
- if (p.is_rtl())
- cursor_vis_width = get_rev_str_width(
- vis.begin() + start + cursor_vis_line_pos + 1,
- line_len - cursor_vis_line_pos - 1);
- else
- cursor_vis_width = get_str_width(
- vis.begin() + start,
- cursor_vis_line_pos);
- }
- return cursor_vis_width;
- }
- // move_to_vis_column() - positions the cursor at a visual column.
- // It does a visual-to-logical conversion and then sums the widths of the
- // characters, in visual order, till it reachs the right column. then
- // it converts this visual column to a logical one.
- void EditBox::move_to_vis_column(int column)
- {
- Paragraph &p = *curr_para();
- // Find the start and the end of the screen line.
- int inner_line = calc_inner_line();
- idx_t start = (inner_line > 0)
- ? p.line_breaks[inner_line - 1]
- : 0;
- idx_t end = p.line_breaks[inner_line];
- idx_t line_len = end - start;
- LevelsArray &levels = cache.levels;
- IdxArray &position_V_to_L = cache.position_V_to_L;
- unistring &vis = cache.vis;
- // We apply the BiDi algorithm if the
- // results are not already cached.
- if (!cache.owned_by(cursor.para)) {
- cache.invalidate(); // it's owned by someone else
- levels.resize(p.str.len());
- DBG(100, ("get_embedding_levels() - by move_to_vis_column()\n"));
- BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
- p.base_dir(), levels.begin(),
- p.breaks_count(), p.line_breaks.begin(),
- !bidi_enabled);
- vis = p.str;
- position_V_to_L.resize(p.str.len());
- // reorder just the screen line.
- reorder(levels.begin() + start, line_len,
- position_V_to_L.begin() + start, NULL, vis.begin() + start);
- }
- if (p.is_rtl()) {
- // revrse the visual string so that we can use the same loop
- // for both RTL and LTR lines.
- reverse(vis.begin() + start, line_len);
- }
-
- // sum the widths of the charaters, in visual order, till we reach
- // the right [visual] column.
- int cursor_vis_line_pos = 0; // silence the compiler
- int width = 0;
- idx_t i;
- wdstate stt;
- for (i = 0; i < line_len; i++) {
- width += get_char_width(vis[start + i], width, &stt, !p.is_rtl());
- if (width > column) {
- cursor_vis_line_pos = i;
- break;
- }
- }
-
- if (p.is_rtl()) {
- // cancel the reverse() we did, because the cache is reused latter.
- reverse(vis.begin() + start, line_len);
- }
- // find the logical column corresponding to the visual column.
- int cursor_log_line_pos;
- if (i == line_len) { // cursor at end of line?
- if (inner_line == p.breaks_count() - 1) { // the last inner line?
- // yes, stand past end of line
- cursor_log_line_pos = cursor_vis_line_pos = line_len;
- } else {
- // no, stand on the last char of the line
- cursor_log_line_pos = cursor_vis_line_pos = line_len - 1;
- }
- } else {
- // use the V_to_L mapping to find the logical column.
- if (p.is_rtl()) {
- cursor_vis_line_pos = line_len - cursor_vis_line_pos - 1;
- cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
- } else {
- cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
- }
- }
- cursor.pos = start + cursor_log_line_pos;
- }
- int EditBox::get_text_width() const
- {
- if (!terminal::is_interactive())
- return non_interactive_text_width;
- else
- return window_width() - margin_before - margin_after;
- }
- // wrap_para() - wraps a paragraph. that means to populate the line_breaks
- // array. this method is called when a paragraph has changed or when various
- // display options that affect the width of some characters are changed.
- void EditBox::wrap_para(Paragraph ¶)
- {
- para.line_breaks.clear();
-
- if (wrap_type == wrpOff) {
- para.line_breaks.push_back(para.str.len());
- return;
- }
-
- int visible_text_width = get_text_width();
- int line_width = 0;
- idx_t line_len = 0;
- wdstate stt;
- for (idx_t i = 0; i < para.str.len(); i++) {
- int char_width = get_char_width(para.str[i], line_width, &stt);
- if ( (line_width + char_width > visible_text_width && line_len > 0)
- || para.str[i] == UNICODE_LS )
- {
- if (para.str[i] == UNICODE_LS) {
- ; // do nothing: break after LS; don't trace back to wspace
- } else if (wrap_type == wrpAtWhiteSpace) {
- // avoid breaking words: break at the previous wspace
- idx_t saved_i = i;
- while (line_len > 0
- && (!BiDi::is_space(para.str[i])
- || i == para.str.len() - 1)) {
- i--;
- line_len--;
- }
- if (line_len == 0) {
- // no wspace found; we have to break the word
- i = saved_i - 1;
- }
- } else { // wrpAnywhere
- i--;
- }
- para.line_breaks.push_back(i + 1);
- line_len = 0;
- line_width = 0;
- stt.clear();
- } else {
- line_len++;
- line_width += char_width;
- }
- }
- // add the end-of-paragraph to line_breaks.
- // first make sure it's not already there (e.g. when the paragraph
- // terminates in a LS).
- if (para.line_breaks.empty() || para.line_breaks.back() != para.str.len())
- para.line_breaks.push_back(para.str.len());
- }
- void EditBox::rewrap_all()
- {
- for (int i = 0; i < parags_count(); i++)
- wrap_para(*paragraphs[i]);
- if (wrap_type == wrpOff) {
- // if we've just turned wrap off, make sure the top inner line is 0.
- top_line.inner_line = 0;
- }
- scroll_to_cursor_line();
- request_update(rgnAll);
- }
- void EditBox::reformat()
- {
- rewrap_all();
- }
- void EditBox::set_wrap_type(WrapType value)
- {
- wrap_type = value;
- rewrap_all();
- NOTIFY_CHANGE(wrap);
- }
- INTERACTIVE void EditBox::toggle_wrap()
- {
- switch (wrap_type) {
- case wrpOff: set_wrap_type(wrpAtWhiteSpace); break;
- case wrpAtWhiteSpace: set_wrap_type(wrpAnywhere); break;
- case wrpAnywhere: set_wrap_type(wrpOff); break;
- }
- }
- void EditBox::set_tab_width(int value)
- {
- if (value > 0) {
- tab_width = value;
- rewrap_all();
- }
- }
- void EditBox::resize(int lines, int columns, int y, int x)
- {
- Widget::resize(lines, columns, y, x);
- if (old_width != columns) { // has the width changed?
- rewrap_all();
- } else {
- // No, no need to rewrap
- scroll_to_cursor_line();
- request_update(rgnAll);
- }
- old_width = columns;
- }
- // }}}
- // Syntax Highlighting {{{
- void EditBox::set_syn_hlt(syn_hlt_t syn)
- {
- syn_hlt = syn;
- request_update(rgnAll);
- }
- INTERACTIVE void EditBox::menu_set_syn_hlt_none()
- {
- set_syn_hlt(synhltOff);
- }
- INTERACTIVE void EditBox::menu_set_syn_hlt_html()
- {
- set_syn_hlt(synhltHTML);
- }
- INTERACTIVE void EditBox::menu_set_syn_hlt_email()
- {
- set_syn_hlt(synhltEmail);
- }
- void EditBox::set_underline(bool v)
- {
- underline_hlt = v;
- request_update(rgnAll);
- }
- INTERACTIVE void EditBox::toggle_underline()
- {
- set_underline(!get_underline());
- }
- // detect_syntax() tries to detect the syntax of the buffer (and turn on
- // the appropriate highlighting). If 2 or more lines starting with ">"
- // are found within the first 10 lines, it's assumed to be an email
- // message. If "<html" or "<HTML" is found within the first 5 lines, it's
- // assumed to be HTML.
- void EditBox::detect_syntax()
- {
- syn_hlt_t syntax = synhltOff;
- int quote_count = 0;
- for (int i = 0; i < 10 && i < parags_count(); i++) {
- const unistring &str = paragraphs[i]->str;
- idx_t len = str.len();
- idx_t pos = 0;
- while (pos < len && str[pos] == ' ')
- pos++;
- if (pos < len && str[pos] == '>')
- quote_count++;
- }
- if (quote_count >= 2) {
- syntax = synhltEmail;
- } else {
- for (int i = 0; i < 5 && i < parags_count(); i++) {
- if ((paragraphs[i]->str.index(u8string("<html")) != -1) ||
- (paragraphs[i]->str.index(u8string("<HTML")) != -1))
- {
- syntax = synhltHTML;
- break;
- }
- }
- }
- set_syn_hlt(syntax);
- }
- // Highlight HTML
- static void highlight_html(const unistring &str, EditBox::AttributeArray& attributes)
- {
- idx_t len = str.len();
- attribute_t html_attr = get_attr(EDIT_HTML_TAG_ATTR);
- bool is_color = contains_color(html_attr);
- bool in_tag = false;
- for (idx_t pos = 0; pos < len; pos++) {
- if (str[pos] == '<')
- in_tag = true;
- if (in_tag) {
- if (is_color)
- attributes[pos] = html_attr;
- else
- attributes[pos] |= html_attr;
- }
- if (str[pos] == '>')
- in_tag = false;
- }
- }
- // Highlight Email
- #define MAX_QUOTE_LEVEL 9
- static void highlight_email(const unistring &str, EditBox::AttributeArray& attributes)
- {
- idx_t len = str.len();
- attribute_t quote_attr = A_NORMAL; // silence the compiler.
- bool is_color = false;
- idx_t pos = 0;
- int level = 0;
- while (pos < len && str[pos] == ' ')
- pos++;
- if (pos < len && str[pos] == '>')
- while (pos < len) {
- if (str[pos] == '>' && level < MAX_QUOTE_LEVEL) {
- level++;
- quote_attr = get_attr(EDIT_EMAIL_QUOTE1_ATTR + level - 1);
- is_color = contains_color(quote_attr);
- }
- if (is_color)
- attributes[pos] = quote_attr;
- else
- attributes[pos] |= quote_attr;
- pos++;
- }
- }
- inline bool is_ltr_alpha(unichar ch)
- {
- return BiDi::is_alnum(ch) && !BiDi::is_rtl(ch);
- }
- // Highlight underline (*text* and _text_)
- static void highlight_underline(const unistring &str, EditBox::AttributeArray& attributes)
- {
- idx_t len = str.len();
- attribute_t underline_attr = get_attr(EDIT_EMPHASIZED_ATTR);
- bool is_color = contains_color(underline_attr);
- bool in_emph = false;
- for (idx_t pos = 0; pos < len; pos++) {
- bool emph_ch = (str[pos] == '_' || str[pos] == '*');
- if (emph_ch) {
- // ignore "http://host/show_bug.cgi" and "ticks_per_second" by making
- // sure it's not LTR alphanumerics on both sides.
- if (pos > 0 && is_ltr_alpha(str[pos-1]) &&
- pos < len-1 && is_ltr_alpha(str[pos+1]))
- emph_ch = false;
- }
- if (emph_ch)
- in_emph = !in_emph;
- if (in_emph || emph_ch) {
- if (is_color)
- attributes[pos] = underline_attr;
- else
- attributes[pos] |= underline_attr;
- }
- }
- }
- // }}}
- // low-level drawing {{{
- // get_char_repr() - get representation of some undisplayable characters,
- // EOPs, and of some interface elements (e.g. '$').
- unichar EditBox::get_char_repr(unichar ch)
- {
- if (reprtbl.translate_char(ch)) {
- return ch;
- } else {
- if (reprtbl.empty())
- return 'X';
- else
- return ch;
- }
- }
- // draw_unistr() - draws an LTR visual string.
- // latin1_transliterate() - when not in UTF-8 locale, we transliterate
- // some Unicode characters to make them readable.
- static unichar latin1_transliterate(unichar ch)
- {
- switch (ch) {
- // Note: 0xB4 (SPACING ACCENT) and 0xA8 (SPACING DIARESIS) cause
- // some troubles on my Linux console when not in UTF-8 locale, probably
- // because the console driver erroneously thinks it's a non-spacing
- // mark (as U+0301 and U+0308 are).
- case 0xB4:
- case 0xA8:
- return 'x';
- case 0x203E:
- return 0xAF; // OVERLINE (we transliterate to "macron" because
- // it's similar)
- case UNI_HEB_MAQAF:
- case UNI_HYPHEN:
- case UNI_NON_BREAKING_HYPHEN:
- case UNI_EN_DASH:
- case UNI_EM_DASH:
- case UNI_MINUS_SIGN:
- return '-';
- case UNI_HEB_GERESH:
- case UNI_RIGHT_SINGLE_QUOTE:
- case UNI_SINGLE_LOW9_QUOTE:
- case UNI_SINGLE_HIGH_REV9_QUOTE:
- return '\'';
- case UNI_LEFT_SINGLE_QUOTE:
- return '`';
-
- case UNI_HEB_GERSHAYIM:
- case UNI_LEFT_DOUBLE_QUOTE:
- case UNI_RIGHT_DOUBLE_QUOTE:
- case UNI_DOUBLE_LOW9_QUOTE:
- case UNI_DOUBLE_HIGH_REV9_QUOTE:
- return '"';
- case UNI_HEB_SOF_PASUQ:
- return ':';
- case UNI_HEB_PASEQ:
- return '|';
- case UNI_BULLET:
- return '*';
- }
- return ch;
- }
- void EditBox::draw_unistr(const unichar *str, idx_t len,
- attribute_t *attributes, int *tab_widths)
- {
- unistring buf;
- if (terminal::do_arabic_shaping) {
- buf.append(str, len);
- len = shape(buf.begin(), len, attributes);
- buf.resize(len);
- str = buf.begin();
- }
-
- #define SETWATTR(_attr) \
- if (current_attr != int(_attr)) { \
- wattrset(wnd, _attr); \
- current_attr = _attr; \
- }
- #ifdef HAVE_WIDE_CURSES
- #define put_unichar_attr(_wch, _attr) \
- do { \
- unichar wch = _wch; \
- if (!terminal::is_utf8) { \
- wch = latin1_transliterate(wch); \
- if (WCTOB(wch) == EOF) { \
- wch = get_char_repr(FAILED_CONV_REPR); \
- /*def_attr |= FAILED_CONV_COLOR;*/ \
- if (contains_color(def_attr)) \
- def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
- else \
- def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
- } \
- } \
- /*SETWATTR(_attr | def_attr);*/ \
- SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
- waddnwstr(wnd, (wchar_t*)&wch, 1); \
- } while (0)
- #else
- #define put_unichar_attr(_wch, _attr) \
- do { \
- int ich = latin1_transliterate(_wch); \
- ich = terminal::force_iso88598 ? unicode_to_iso88598(ich) : WCTOB(ich); \
- if (ich == EOF) { \
- ich = get_char_repr(FAILED_CONV_REPR); \
- /*def_attr |= FAILED_CONV_COLOR;*/ \
- if (contains_color(def_attr)) \
- def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
- else \
- def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
- } \
- /*SETWATTR(_attr | def_attr);*/ \
- SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
- waddch(wnd, (unsigned char)ich); \
- } while (0)
- #endif
- int tab_counter = 0;
- int line_width = 0;
- int current_attr = -1;
- for (idx_t i = 0; i < len; i++) {
- unichar ch = str[i];
- int char_width = IS_SIMPLE_ONE_COLUMN_CHAR(ch) ? 1 : mk_wcwidth(ch);
- int def_attr = attributes ? attributes[i] : A_NORMAL;
- if (char_width < 1) {
- if (ch == '\t') {
- if (tab_widths)
- char_width = tab_widths[tab_counter++];
- else
- char_width = tab_width - (line_width % tab_width);
- for (int t = 0; t < char_width; t++) {
- if (show_tabs)
- put_unichar_attr(get_char_repr('\t'), get_attr(EDIT_TAB_ATTR));
- else
- put_unichar_attr(' ', 0);
- }
- } else if (char_width == -1 || ch == '\0') {
- // Control characters / nulls: print symbol instead
- char_width = 1;
- put_unichar_attr(get_char_repr(CONTROL_REPR), get_attr(EDIT_CONTROL_ATTR));
- } else {
- // Non-Spacing Marks.
- // First, handle BIDI explicits and Hebrew NSMs
- if (BiDi::is_explicit_mark(ch)) {
- if (show_explicits) {
- char_width = 1;
- put_unichar_attr(get_char_repr(ch), get_attr(EDIT_EXPLICIT_ATTR));
- }
- } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
- if (rtl_nsm_display == rtlnsmTransliterated) {
- char_width = 1;
- put_unichar_attr(get_char_repr(ch),
- BiDi::is_cantillation_nsm(ch)
- ? get_attr(EDIT_NSM_CANTILLATION_ATTR)
- : (BiDi::is_arabic_nsm(ch)
- ? get_attr(EDIT_NSM_ARABIC_ATTR)
- : get_attr(EDIT_NSM_HEBREW_ATTR)) );
- } else if (rtl_nsm_display == rtlnsmAsis
- && terminal::is_utf8 && !terminal::is_fixed) {
- put_unichar_attr(ch, 0);
- }
- } else {
- // Now handle general NSMs
- if (!terminal::is_utf8 || terminal::is_fixed) {
- char_width = 1;
- put_unichar_attr(get_char_repr(NSM_REPR), get_attr(EDIT_NSM_ATTR));
- } else {
- // :TODO: new func: is_printable_zw()
- if (ch != UNI_ZWNJ && ch != UNI_ZWJ) {
- put_unichar_attr(ch, 0);
- }
- }
- }
- }
- } else {
- if (char_width == 2 && (!terminal::is_utf8 || terminal::is_fixed)) {
- // Wide Asian ideograph, but terminal is not capable
- char_width = 1;
- put_unichar_attr(get_char_repr(WIDE_REPR), get_attr(EDIT_WIDE_ATTR));
- } else {
- attribute_t xattr = 0;
- if (ch == UNICODE_LS) {
- ch = get_char_repr(UNICODE_LS);
- xattr = get_attr(EDIT_UNICODE_LS_ATTR);
- } else if (ch == UNI_NO_BREAK_SPACE) {
- ch = ' ';
- } else if (ch == UNI_HEB_MAQAF) {
- if (maqaf_display == mqfTransliterated
- || maqaf_display == mqfHighlighted)
- ch = get_char_repr(UNI_HEB_MAQAF);
- if (maqaf_display == mqfHighlighted)
- xattr = get_attr(EDIT_MAQAF_ATTR);
- }
- put_unichar_attr(ch, xattr);
- }
- }
- line_width += char_width;
- }
- SETWATTR(A_NORMAL);
- #undef SETWATTR
- #undef put_unichar_attr
- }
- void EditBox::calc_tab_widths(const unichar *str, idx_t len,
- bool rev, IntArray &tab_widths)
- {
- tab_widths.clear();
- int line_width = 0;
- wdstate stt;
- idx_t i = rev ? len - 1 : 0;
- while (len--) {
- int char_width = get_char_width(str[i], line_width, &stt);
- if (str[i] == '\t') {
- if (rev)
- tab_widths.insert(tab_widths.begin(), char_width);
- else
- tab_widths.push_back(char_width);
- }
- line_width += char_width;
- i += rev ? -1 : 1;
- }
- }
- // draw_rev_unistr() - draws an RTL visual string. it calls draw_string()
- // to do the actuall work. The two functions differ in how they
- // calculate TAB widths. draw_rev_unistr() is neccessary because in RTL
- // visual strings the first column is at the end of the string. it
- // calculates the TAB widths accordingly and passes them to draw_unistring().
- void EditBox::draw_rev_unistr(const unichar *str, idx_t len,
- attribute_t *attributes)
- {
- IntArray tab_widths;
- calc_tab_widths(str, len, true, tab_widths);
- draw_unistr(str, len, attributes, tab_widths.begin());
- }
- void EditBox::put_unichar_attr_at(int line, int col, unichar ch, int attr)
- {
- wattrset(wnd, attr);
- wmove(wnd, line, col);
- put_unichar(ch, get_char_repr(FAILED_CONV_REPR));
- }
- // draw_eop() - draws an EOP symbol.
- void EditBox::draw_eop(int y, int x, Paragraph &p, bool selected)
- {
- if (!show_paragraph_endings)
- return;
- unichar ch;
- switch (p.eop) {
- case eopMac: ch = EOP_MAC_REPR; break;
- case eopDOS: ch = EOP_DOS_REPR; break;
- case eopUnix: ch = p.is_rtl()
- ? EOP_UNIX_RTL_REPR
- : EOP_UNIX_LTR_REPR;
- break;
- case eopUnicode:
- ch = EOP_UNICODE_REPR; break;
- default:
- ch = EOP_NONE_REPR;
- }
- put_unichar_attr_at(y, x, get_char_repr(ch),
- get_attr(EDIT_EOP_ATTR) | (selected ? A_REVERSE : 0));
- }
- // }}}
- // redraw_paragraph {{{
- void EditBox::redraw_unwrapped_paragraph(
- Paragraph &p,
- int window_start_line,
- bool only_cursor,
- int para_num,
- LevelsArray &levels,
- IdxArray& position_L_to_V,
- IdxArray& position_V_to_L,
- AttributeArray& attributes,
- bool eop_is_selected
- )
- {
- unistring &vis = cache.vis;
- if (!cache.owned_by(para_num)) {
- vis = p.str;
- reorder(levels.begin(), p.str.len(),
- position_V_to_L.begin(),
- position_L_to_V.begin(),
- vis.begin(),
- attributes.begin(), do_mirror,
- (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
- }
-
- // Step 1. find out the start and end of the
- // segment visible on screen.
- // note: we use the term "col", or "pos", to refer to character index, and
- // "width" to refer to the visual position on screen -- sum of the widths
- // of the preceeding character.
- // find the visual cursor index
- int cursor_vis_width = 0;
- idx_t cursor_vis_line_pos = -1;
- if (curr_para() == &p) {
- if (cursor.pos == p.str.len())
- cursor_vis_line_pos = p.str.len();
- else {
- cursor_vis_line_pos = position_L_to_V[cursor.pos];
- if (p.is_rtl())
- cursor_vis_line_pos = p.str.len() - cursor_vis_line_pos - 1;
- }
- }
- idx_t start_col = 0, end_col = 0;
- int visible_text_width = get_text_width();
- int segment_width = 0;
-
- if (p.is_rtl()) {
- reverse(vis.begin(), p.str.len());
- reverse(attributes.begin(), p.str.len());
- }
- idx_t i;
- wdstate stt;
- for (i = 0; i < p.str.len(); i++) {
- int char_width = get_char_width(vis[i], segment_width, &stt, !p.is_rtl());
- segment_width += char_width;
- if (segment_width > visible_text_width) {
- if (cursor_vis_line_pos < i) {
- // we've found the end of the segment.
- segment_width -= char_width;
- end_col = i;
- break;
- } else {
- // start a new segment. recalculate the width of this
- // first character because TAB's width may change.
- stt.clear();
- char_width = get_char_width(vis[i], 0, &stt, !p.is_rtl());
- segment_width = char_width;
- start_col = i;
- }
- }
- if (i == cursor_vis_line_pos)
- cursor_vis_width = segment_width - char_width;
- }
- if (end_col == 0)
- end_col = i;
- if (cursor.pos == p.str.len()) {
- cursor_vis_width = segment_width;
- }
- if (p.is_rtl()) {
- reverse(vis.begin() + start_col, end_col - start_col);
- reverse(attributes.begin() + start_col, end_col - start_col);
- }
-
- // Step 2. draw the segment [start_col .. end_col)
- if (p.is_rtl()) {
- wmove(wnd, window_start_line,
- margin_after + visible_text_width - segment_width);
- draw_rev_unistr(vis.begin() + start_col, end_col - start_col,
- attributes.begin() + start_col);
- } else {
- wmove(wnd, window_start_line, margin_before);
- draw_unistr(vis.begin() + start_col, end_col - start_col,
- attributes.begin() + start_col);
- }
- if (p.is_rtl()) {
- // cancel the reverse() we did, because the cache is reused latter.
- reverse(vis.begin() + start_col, end_col - start_col);
- reverse(vis.begin(), p.str.len());
- }
- // Step 3. draw EOP / continuation indicator
- if (end_col != p.str.len()) {
- // if the end of the para is not shown,
- // draw line-countinuation indicator ('$')
- put_unichar_attr_at(
- window_start_line,
- p.is_rtl() ? margin_after - 1
- : margin_before + visible_text_width,
- get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
- } else {
- // if the end is shown, draw EOP
- draw_eop(window_start_line,
- p.is_rtl() ? margin_after + visible_text_width
- - segment_width - 1
- : margin_before + segment_width,
- p, eop_is_selected);
- }
-
- if (start_col != 0 && cursor_vis_width > 2) {
- // if the beginning of the para is not shown,
- // draw another line-continuation indicator, at the other side.
- put_unichar_attr_at(
- window_start_line,
- p.is_rtl() ? margin_after + visible_text_width - 1
- : margin_before,
- get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
- }
- // Step 4. position the cursor
- if (p.is_rtl()) {
- cursor_vis_width = visible_text_width - cursor_vis_width - 1;
- wmove(wnd, window_start_line, margin_after + cursor_vis_width);
- } else {
- wmove(wnd, window_start_line, margin_before + cursor_vis_width);
- }
- }
- void EditBox::redraw_wrapped_paragraph(
- Paragraph &p,
- int window_start_line,
- bool only_cursor,
- int para_num,
- LevelsArray &levels,
- IdxArray& position_L_to_V,
- IdxArray& position_V_to_L,
- AttributeArray& attributes,
- bool eop_is_selected
- )
- {
- idx_t cursor_vis_line_pos = -1;
- int cursor_vis_width = 0;
- int cursor_line = -1;
- unistring &vis = cache.vis;
- if (!cache.owned_by(para_num))
- vis = p.str;
-
- int visible_text_width = get_text_width();
- idx_t prev_line_break = 0;
- // draw the paragraph line by line.
- for (int line_num = 0;
- line_num < p.breaks_count();
- line_num++)
- {
- idx_t line_break = p.line_breaks[line_num];
- idx_t line_len = line_break - prev_line_break;
- bool is_last_line = (line_num == p.breaks_count() - 1);
- if (!cache.owned_by(para_num)) {
- reorder(levels.begin() + prev_line_break, line_len,
- position_V_to_L.begin() + prev_line_break,
- position_L_to_V.begin() + prev_line_break,
- vis.begin() + prev_line_break,
- attributes.begin() + prev_line_break, do_mirror,
- (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
- }
- // draw segment [prev_line_break .. line_break)
- if ( !only_cursor && window_start_line + line_num >= 0
- && window_start_line + line_num < window_height() ) {
- // :TODO: handle TABS at end of line (especially RTL lines)
- if (p.is_rtl()) {
- int line_width = get_rev_str_width(
- vis.begin() + prev_line_break, line_len);
- int window_x = margin_after;
- // we reserve one space for wrap (margin_after), so:
- if (line_width > visible_text_width)
- window_x--;
- else
- window_x += visible_text_width - line_width;
- wmove(wnd, window_start_line + line_num, window_x);
- draw_rev_unistr(vis.begin() + prev_line_break, line_len,
- attributes.begin() + prev_line_break);
- if (is_last_line) {
- draw_eop(window_start_line + line_num, window_x - 1,
- p, eop_is_selected);
- } else if (wrap_type == wrpAnywhere) {
- put_unichar_attr_at(
- window_start_line + line_num,
- margin_after - 1,
- get_char_repr(WRAP_RTL_REPR), get_attr(EDIT_WRAP_ATTR));
- }
- } else {
- wmove(wnd, window_start_line + line_num, margin_before);
- draw_unistr(vis.begin() + prev_line_break, line_len,
- attributes.begin() + prev_line_break);
- if (is_last_line) {
- draw_eop(window_start_line + line_num,
- margin_before + get_str_width(vis.begin()
- + prev_line_break, line_len),
- p, eop_is_selected);
- } else if (wrap_type == wrpAnywhere) {
- put_unichar_attr_at(
- window_start_line + line_num,
- margin_before + visible_text_width,
- get_char_repr(WRAP_LTR_REPR), get_attr(EDIT_WRAP_ATTR));
- }
- }
- }
- // find the visual cursor position
- if (curr_para() == &p && cursor_line == -1) {
- if (cursor.pos < line_break) {
- int cursor_log_line_pos = cursor.pos - prev_line_break;
- cursor_vis_line_pos = position_L_to_V[
- prev_line_break + cursor_log_line_pos];
- if (p.is_rtl()) {
- cursor_vis_width = get_rev_str_width(
- vis.begin() + prev_line_break
- + cursor_vis_line_pos + 1,
- line_len - cursor_vis_line_pos - 1);
- } else {
- cursor_vis_width = get_str_width(
- vis.begin() + prev_line_break,
- cursor_vis_line_pos);
- }
- cursor_line = line_num;
- }
- else if (cursor.pos == p.str.len() && is_last_line) {
- if (p.is_rtl()) {
- cursor_vis_width = get_rev_str_width(
- vis.begin() + prev_line_break,
- line_len);
- } else {
- cursor_vis_width = get_str_width(
- vis.begin() + prev_line_break,
- line_len);
- }
- cursor_line = line_num;
- }
- }
- prev_line_break = line_break;
- }
- // position the cursor
- if (cursor_line != -1) {
- if (p.is_rtl()) {
- cursor_vis_width = visible_text_width - cursor_vis_width - 1;
- wmove(wnd,
- window_start_line + cursor_line,
- margin_after + cursor_vis_width);
- } else {
- wmove(wnd,
- window_start_line + cursor_line,
- margin_before + cursor_vis_width);
- }
- }
- }
- void EditBox::do_syntax_highlight(const unistring &str,
- AttributeArray &attributes, int para_num)
- {
- switch (syn_hlt) {
- case synhltHTML:
- highlight_html(str, attributes);
- break;
- case synhltEmail:
- highlight_email(str, attributes);
- break;
- default:
- break;
- }
- if (underline_hlt)
- highlight_underline(str, attributes);
- }
-
- void EditBox::redraw_paragraph(Paragraph &p, int window_start_line,
- bool only_cursor, int para_num)
- {
- //if (is_primary_mark_set()
- //cache.invalidate();
- if (is_primary_mark_set()
- || wrap_type == wrpOff) // FIXME: when wrap=off, highlighting fucks up in RTL lines;
- // cache.invalidate() is a temporary solution.
- cache.invalidate();
-
- LevelsArray &levels = cache.levels;
- IdxArray &position_L_to_V = cache.position_L_to_V;
- IdxArray &position_V_to_L = cache.position_V_to_L;
- AttributeArray &attributes = cache.attributes;
- if (!cache.owned_by(para_num)) {
- position_L_to_V.resize(p.str.len());
- position_V_to_L.resize(p.str.len());
- levels.resize(p.str.len());
- attributes.clear();
- attributes.resize(p.str.len());
- DBG(100, ("get_embedding_levels() - by redraw_paragraph()\n"));
- BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
- p.base_dir(), levels.begin(),
- p.breaks_count(), p.line_breaks.begin(),
- !bidi_enabled);
- do_syntax_highlight(p.str, attributes, para_num);
- }
- //AttributeArray attributes(p.str.len());
- bool eop_is_selected = false;
- // We use the "attributes" array to highlight the selection.
- // for each selected characetr, we "OR" the corresponding
- // attribute element with A_REVERSE.
- if (is_primary_mark_set()) {
- Point lo = primary_mark, hi = cursor;
- if (hi < lo)
- hi.swap(lo);
- if (lo.para <= para_num && hi.para >= para_num) {
- idx_t start, end;
- if (lo.para == para_num)
- start = lo.pos;
- else
- start = 0;
- if (hi.para == para_num)
- end = hi.pos;
- else {
- eop_is_selected = true;
- end = p.str.len();
- }
- attribute_t selected_attr = get_attr(EDIT_SELECTED_ATTR);
- idx_t i;
- if (contains_color(selected_attr))
- for (i = start; i < end; i++)
- attributes[i] = selected_attr;
- else
- for (i = start; i < end; i++)
- attributes[i] |= selected_attr;
- }
- }
- if (wrap_type == wrpOff) {
- redraw_unwrapped_paragraph(
- p,
- window_start_line,
- only_cursor,
- para_num,
- levels,
- position_L_to_V,
- position_V_to_L,
- attributes,
- eop_is_selected
- );
- } else {
- redraw_wrapped_paragraph(
- p,
- window_start_line,
- only_cursor,
- para_num,
- levels,
- position_L_to_V,
- position_V_to_L,
- attributes,
- eop_is_selected
- );
- }
- cache.set_owner(para_num);
- }
- // }}}
- // Log2Vis {{{
- // emph_string() - emphasize marked-up segments. I.e. converts
- // "the *quick* fox" to "the q_u_i_c_k_ fox". but "* bullet"
- // stays "* bullet".
- static void emph_string(unistring &str,
- unichar emph_marker, unichar emph_ch)
- {
- bool in_emph = false;
- for (int i = 0; i < str.len(); i++) {
- if ( (!emph_marker && (str[i] == '*' || str[i] == '_')) ||
- ( emph_marker && str[i] == emph_marker ) ) {
- if (!(!in_emph &&
- i < str.len()-1 &&
- BiDi::is_space(str[i+1])) )
- {
- in_emph = !in_emph;
- str.erase(str.begin() + i);
- i--;
- }
- } else {
- if (in_emph) {
- i++;
- // skip all NSMs.
- while (i < str.len() && BiDi::is_nsm(str[i]))
- i++;
- str.insert(str.begin()+i, emph_ch);
- }
- }
- }
- }
- // log2vis() - convert the [logical] document into visual.
- void EditBox::log2vis(const char *options)
- {
- bool opt_bdo = false;
- bool opt_nopad = false;
- bool opt_engpad = false;
- unichar opt_emph = false;
- unichar opt_emph_marker = 0;
- unichar opt_emph_ch = UNI_NS_UNDERSCORE;
- // parse the 'options' string.
- if (options) {
- if (strstr(options, "bdo"))
- opt_bdo = true;
- if (strstr(options, "nopad"))
- opt_nopad = true;
- if (strstr(options, "engpad"))
- opt_engpad = true;
- char *s;
- if ((s = strstr(options, "emph"))) {
- opt_emph = true;
- if (s[4] == ':') {
- opt_emph_ch = strtol(s + 5, &s, 0);
- if (s && (*s == ':' || *s == ','))
- opt_emph_marker = strtol(s + 1, NULL, 0);
- }
- DBG(1, ("--EMPH-- glyph: U+%04lX, marker: U+%04lX\n",
- (unsigned long)opt_emph_ch,
- (unsigned long)opt_emph_marker));
- }
- }
-
- std::vector<unistring> visuals;
-
- for (int i = 0; i < parags_count(); i++)
- {
- Paragraph &p = *paragraphs[i];
- unistring &visp = p.str;
- if (opt_emph) {
- emph_string(visp, opt_emph_marker, opt_emph_ch);
- post_para_modification(p);
- }
-
- cache.invalidate();
- LevelsArray &levels = cache.levels;
- IdxArray &position_L_to_V = cache.position_L_to_V;
- IdxArray &position_V_to_L = cache.position_V_to_L;
- position_L_to_V.resize(p.str.len());
- position_V_to_L.resize(p.str.len());
- levels.resize(p.str.len());
- DBG(100, ("get_embedding_levels() - by log2vis()\n"));
- BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
- p.base_dir(), levels.begin(),
- p.breaks_count(), p.line_breaks.begin());
- int visible_text_width = get_text_width();
- idx_t prev_line_break = 0;
- for (int line_num = 0;
- line_num < p.breaks_count();
- line_num++)
- {
- idx_t line_break = p.line_breaks[line_num];
- idx_t line_len = line_break - prev_line_break;
-
- // trim
- while (line_len
- && BiDi::is_space(p.str[prev_line_break + line_len - 1]))
- line_len--;
- // convert to visual
- reorder(levels.begin() + prev_line_break, line_len,
- position_V_to_L.begin() + prev_line_break,
- position_L_to_V.begin() + prev_line_break,
- visp.begin() + prev_line_break,
- NULL, true,
- rtl_nsm_display == rtlnsmAsis);
- if (terminal::do_arabic_shaping)
- line_len = shape(p.str.begin() + prev_line_break,
- line_len, NULL);
- unistring visline;
- // pad RTL lines
- if (p.is_rtl() && !opt_nopad) {
- int line_width = get_rev_str_width(
- visp.begin() + prev_line_break, line_len);
- while (line_width < visible_text_width) {
- visline.push_back(' ');
- line_width++;
- }
- }
- // convert TABs to spaces, and convert/erase some
- // special chars.
- IntArray tab_widths;
- int tab_counter = 0;
- calc_tab_widths(visp.begin() + prev_line_break, line_len,
- p.is_rtl(), tab_widths);
- for (int i = prev_line_break; i < prev_line_break + line_len; i++) {
- if (visp[i] == '\t') {
- int j = tab_widths[tab_counter++];
- while (j--)
- visline.push_back(' ');
- } else if (visp[i] == UNI_HEB_MAQAF
- && maqaf_display != mqfAsis) {
- visline.push_back('-');
- } else {
- if (!BiDi::is_transparent_formatting_code(visp[i]))
- visline.push_back(visp[i]);
- }
- }
- // pad LTR lines
- if (!p.is_rtl() && opt_engpad) {
- int line_width = get_str_width(
- visp.begin() + prev_line_break, line_len);
- while (line_width < visible_text_width) {
- visline.push_back(' ');
- line_width++;
- }
- }
-
- if (opt_bdo) {
- visline.insert(visline.begin(), UNI_LRO);
- visline.push_back(UNI_PDF);
- }
- visuals.push_back(visline);
-
- prev_line_break = line_break;
- }
- delete paragraphs[i];
- }
- // replace the logical document with the visual version.
- paragraphs.clear();
- for (int i = 0; i < (int)visuals.size(); i++) {
- Paragraph *p = new Paragraph();
- p->str = visuals[i];
- p->eop = eopUnix;
- post_para_modification(*p);
- // skip the last empty line.
- if (!(i == (int)visuals.size() - 1 && p->str.len() == 0))
- paragraphs.push_back(p);
- }
- undo_stack.clear();
- unset_primary_mark();
- set_modified(true);
- cursor.zero();
- scroll_to_cursor_line();
- request_update(rgnAll);
- }
- // }}}
|