1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762 |
- // 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 <stdlib.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;
- const char *s;
- // FIXME: The following writes on s, which is in the read-only buffer options.
- if (s = strstr(options, "emph")) {
- opt_emph = true;
- if (s[4] == ':') {
- opt_emph_ch = strtol(s + 5, (char**)&s, 0); // FIXME: Brute force. Tzafrir
- 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);
- }
- // }}}
|