editbox2.cc 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762
  1. // Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
  2. //
  3. // This program is free software; you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation; either version 2 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  16. #include <config.h>
  17. #include <stdlib.h>
  18. #include "editbox.h"
  19. #include "transtbl.h"
  20. #include "univalues.h"
  21. #include "mk_wcwidth.h"
  22. #include "my_wctob.h"
  23. #include "shaping.h"
  24. #include "themes.h"
  25. #include "dbg.h"
  26. // Default representations for some characters and concepts.
  27. // If you want to change these, don't edit this file; edit "reprtab" instead.
  28. #define FAILED_CONV_REPR '?'
  29. #define CONTROL_REPR '^'
  30. #define NSM_REPR '\''
  31. #define WIDE_REPR 'A'
  32. #define TRIM_REPR '$'
  33. #define WRAP_RTL_REPR '/'
  34. #define WRAP_LTR_REPR '\\'
  35. #define EOP_UNICODE_REPR 0xB6
  36. #define EOP_DOS_REPR 0xB5
  37. #define EOP_UNIX_LTR_REPR 0xAB
  38. #define EOP_UNIX_RTL_REPR 0xBB
  39. #define EOP_MAC_REPR '@'
  40. #define EOP_NONE_REPR 0xAC
  41. static bool do_mirror = true; // Do mirroring also when !bidi_enabled ?
  42. // Setters and Togglers for various display options {{{
  43. void EditBox::set_formatting_marks(bool value)
  44. {
  45. show_paragraph_endings = value;
  46. show_explicits = value;
  47. show_tabs = value;
  48. rewrap_all(); // width changes because we show/hide explicit marks
  49. NOTIFY_CHANGE(formatting_marks);
  50. }
  51. // toggle_formatting_marks() - interactive command to toggle display of
  52. // formatting marks.
  53. INTERACTIVE void EditBox::toggle_formatting_marks()
  54. {
  55. set_formatting_marks(!has_formatting_marks());
  56. }
  57. void EditBox::set_maqaf_display(maqaf_display_t disp)
  58. {
  59. maqaf_display = disp;
  60. request_update(rgnAll);
  61. NOTIFY_CHANGE(maqaf);
  62. }
  63. // toggle_maqaf() - interactive command to toggle the display of maqaf.
  64. INTERACTIVE void EditBox::toggle_maqaf()
  65. {
  66. switch (maqaf_display) {
  67. case mqfAsis: set_maqaf_display(mqfTransliterated); break;
  68. case mqfTransliterated: set_maqaf_display(mqfHighlighted); break;
  69. case mqfHighlighted: set_maqaf_display(mqfAsis); break;
  70. }
  71. }
  72. bool EditBox::set_rtl_nsm_display(rtl_nsm_display_t disp)
  73. {
  74. if (disp == rtlnsmAsis && (!terminal::is_utf8 || terminal::is_fixed)) {
  75. NOTIFY_ERROR(cant_display_nsm);
  76. return false;
  77. }
  78. rtl_nsm_display = disp;
  79. rewrap_all();
  80. NOTIFY_CHANGE(rtl_nsm);
  81. return true;
  82. }
  83. // toggle_rtl_nsm() - interactive command to toggle display of Hebrew
  84. // points.
  85. INTERACTIVE void EditBox::toggle_rtl_nsm()
  86. {
  87. switch (rtl_nsm_display) {
  88. case rtlnsmOff:
  89. set_rtl_nsm_display(rtlnsmTransliterated);
  90. break;
  91. case rtlnsmTransliterated:
  92. if (set_rtl_nsm_display(rtlnsmAsis))
  93. break;
  94. // fall-through
  95. case rtlnsmAsis:
  96. set_rtl_nsm_display(rtlnsmOff);
  97. break;
  98. }
  99. }
  100. void EditBox::enable_bidi(bool value)
  101. {
  102. bidi_enabled = value;
  103. cache.invalidate();
  104. invalidate_optimal_vis_column();
  105. request_update(rgnAll);
  106. }
  107. INTERACTIVE void EditBox::toggle_bidi()
  108. {
  109. enable_bidi(!is_bidi_enabled());
  110. }
  111. // }}}
  112. // Updating {{{
  113. // request_update() - request that some region be repainted next time the
  114. // update() method is called.
  115. void EditBox::request_update(region rgn)
  116. {
  117. update_region |= rgn;
  118. }
  119. // request_update(from, to) - similar to request_update(rgnAll), but only
  120. // paints the paragraphs in the range [from, to].
  121. void EditBox::request_update(int from, int to)
  122. {
  123. if (update_region & rgnRange) {
  124. // if there's already a pending rgnRange request,
  125. // give up and paint the whole window.
  126. update_region = rgnAll;
  127. } else {
  128. update_region |= rgnRange;
  129. update_from = from;
  130. update_to = to;
  131. }
  132. }
  133. void EditBox::update()
  134. {
  135. if (update_region == rgnNone)
  136. return;
  137. static int last_cursor_para = -1;
  138. if (is_primary_mark_set()) {
  139. // Determining which paragraphs to repaint when a selection is active
  140. // is a bit complicated, so we revert to a simple decision: if the
  141. // cursor hasn't moved from the paragraph we painted in the previous
  142. // update then the selection hasn't moved to include new paragraphs
  143. // and we can paint only this paragraph; else repaint the whole
  144. // window.
  145. if (last_cursor_para == cursor.para) {
  146. // we repaint the whole paragraph even if we're asked to only
  147. // reposition the cursor, because the selection has changed.
  148. update_region |= rgnCurrent;
  149. } else {
  150. update_region = rgnAll;
  151. }
  152. last_cursor_para = cursor.para;
  153. }
  154. if (wrap_type == wrpOff && update_region == rgnCursor) {
  155. // Well... this is a flaw in our update mechanism. In no-wrap mode we
  156. // repaint the whole paragraph even if we're asked to only reposition
  157. // the cursor. that's because the new cursor position might be in a
  158. // segment which is not shown on screen.
  159. update_region = rgnCurrent;
  160. }
  161. if (update_region & rgnAll) {
  162. wbkgd(wnd, get_attr(EDIT_ATTR));
  163. werase(wnd);
  164. // we invalidate the cache here instead of doing it after every change
  165. // that affects the display.
  166. cache.invalidate();
  167. }
  168. int window_line = -top_line.inner_line;
  169. int curr_para_line = 0; // the window line at which the current para starts.
  170. // "=0" to silence the compiler.
  171. for (int i = top_line.para;
  172. i < parags_count()
  173. && window_line < window_height();
  174. i++) {
  175. Paragraph *para = paragraphs[i];
  176. if (para == curr_para()) {
  177. // we paint the current para outside of the loop because its
  178. // painting also positions the cursor (which subsequent draws
  179. // will invalidate).
  180. curr_para_line = window_line;
  181. if (update_region != rgnCursor) {
  182. // we don't paint it yet, but we erase its background
  183. for (int k = window_line;
  184. k < window_line + para->breaks_count()
  185. && k < window_height();
  186. k++) {
  187. wmove(wnd, k, 0);
  188. wclrtoeol(wnd);
  189. }
  190. }
  191. }
  192. else if ((update_region & rgnAll)
  193. || ((update_region & rgnRange)
  194. && i >= update_from && i <= update_to)) {
  195. redraw_paragraph(*para, window_line, false, i);
  196. }
  197. window_line += para->breaks_count();
  198. }
  199. // paint the current paragraph
  200. bool only_cursor = (update_region == rgnCursor);
  201. redraw_paragraph(*curr_para(), curr_para_line, only_cursor, cursor.para);
  202. wnoutrefresh(wnd);
  203. update_region = rgnNone;
  204. }
  205. // }}}
  206. // BiDi Reordering {{{
  207. template <class VAL, class IDX>
  208. inline void reverse(VAL *array, IDX len)
  209. {
  210. if (len == 0)
  211. return; // IDX may be unsigned, so "-1" won't work.
  212. for (IDX start = 0, end = len - 1; start < end; start++, end--) {
  213. VAL tmp = array[start];
  214. array[start] = array[end];
  215. array[end] = tmp;
  216. }
  217. }
  218. // reorder() - Reorder Resolved Levels. Rules L2..L4. Unfortunately, FriBiDi
  219. // has a flawed interface: this should be done after line-wrapping, and
  220. // FriBiDi doesn't have a separate function for reordering, so we have to do
  221. // it ourselves. (but it isn't so bad: that way we are not dependent on the
  222. // BiDi engine, because we use very little of it.)
  223. static void reorder(level_t *levels, idx_t len,
  224. idx_t *position_V_to_L, idx_t *position_L_to_V,
  225. unichar *str,
  226. attribute_t *attributes = NULL,
  227. bool mirror = false,
  228. bool reorder_nsm = false)
  229. {
  230. // We update a V_to_L vector from a L_to_V one. If the user
  231. // doesn't provide a L_to_V vector, we have to allocate it
  232. // ourselves.
  233. EditBox::IdxArray internal_position_V_to_L;
  234. if (position_L_to_V && !position_V_to_L) {
  235. internal_position_V_to_L.resize(len);
  236. position_V_to_L = internal_position_V_to_L.begin();
  237. }
  238. if (position_V_to_L) {
  239. for (idx_t i = 0; i < len; i++)
  240. position_V_to_L[i] = i;
  241. }
  242. level_t highest_level = 0;
  243. level_t lowest_odd_level = 63;
  244. for (idx_t i = 0; i < len; i++) {
  245. level_t level = levels[i];
  246. if (level > highest_level)
  247. highest_level = level;
  248. if ((level & 1) && level < lowest_odd_level)
  249. lowest_odd_level = level;
  250. }
  251. // L2
  252. for (level_t level = highest_level; level >= lowest_odd_level; level--)
  253. for (idx_t i = 0; i < len; i++)
  254. if (levels[i] >= level) {
  255. idx_t start = i;
  256. while (i < len && levels[i] >= level)
  257. i++;
  258. if (position_V_to_L)
  259. reverse(position_V_to_L + start, i - start);
  260. reverse(levels + start, i - start);
  261. if (str)
  262. reverse(str + start, i - start);
  263. if (attributes)
  264. reverse(attributes + start, i - start);
  265. }
  266. // L3, L4: Mirroring and NSM reordering.
  267. if (str && (mirror || reorder_nsm)) {
  268. for (idx_t i = 0; i < len; i++) {
  269. if (levels[i] & 1) {
  270. BiDi::mirror_char(&str[i]);
  271. // we don't unconditionally reorder NSMs bacause when the
  272. // user prefers ASCII transliteration they should be left
  273. // to the left of the character.
  274. if (reorder_nsm
  275. && BiDi::is_nsm(str[i]))
  276. {
  277. idx_t start = i++;
  278. while (i < len && BiDi::is_nsm(str[i]))
  279. i++;
  280. if (i < len) // :FIXME: only if on the same level.
  281. i++; // include the base character.
  282. // should I reorder position_V_to_L and attributes too?
  283. //if (position_V_to_L)
  284. // reverse(position_V_to_L + start, i - start);
  285. reverse(str + start, i - start);
  286. //if (attributes)
  287. // reverse(attributes + start, i - start);
  288. if (i < len)
  289. i--;
  290. }
  291. }
  292. }
  293. }
  294. if (position_L_to_V) {
  295. for (idx_t i = 0; i < len; i++)
  296. position_L_to_V[position_V_to_L[i]] = i;
  297. }
  298. }
  299. // }}}
  300. // Wrap / character traits {{{
  301. // most of the characters will probably be "simple" ones, meaning
  302. // they occupy one terminal column. IS_SIMPLE_ONE_COLUMN_CHAR helps
  303. // us avoid a call to wcwidth().
  304. #define IS_SIMPLE_ONE_COLUMN_CHAR(ch) \
  305. ((ch >= 32 && ch <= 126) \
  306. || (ch >= UNI_HEB_ALEF && ch <= UNI_HEB_TAV))
  307. // get_char_width() - returns the width, in terminal columns, of a character.
  308. // it takes into account the user's preferences (e.g. whether to show explicit
  309. // marks), LAM-ALEF ligature, and the terminal capabilities (e.g. whether it's
  310. // capable of displaying wide and combining characters). it also calculates
  311. // the width of a TAB character based on its position in the line (pos).
  312. int EditBox::get_char_width(unichar ch, int pos, wdstate *stt, bool visual)
  313. {
  314. #define LAM_N 0x0644
  315. #define IS_LAM_N(c) ((c) == LAM_N)
  316. #define ALEF_MADDA_N 0x0622
  317. #define ALEF_HAMZA_ABOVE_N 0x0623
  318. #define ALEF_HAMZA_BELOW_N 0x0625
  319. #define ALEF_N 0x0627
  320. #define IS_ALEF_N(c) ((c) == ALEF_MADDA_N \
  321. || (c) == ALEF_HAMZA_ABOVE_N \
  322. || (c) == ALEF_HAMZA_BELOW_N \
  323. || (c) == ALEF_N)
  324. // optimization
  325. if (IS_SIMPLE_ONE_COLUMN_CHAR(ch) && !(stt && stt->may_start_lam_alef))
  326. return 1;
  327. int width = mk_wcwidth(ch);
  328. // handle ALEF-LAM ligature
  329. if (terminal::do_arabic_shaping && stt) {
  330. if (visual ? IS_ALEF_N(ch) : IS_LAM_N(ch)) {
  331. stt->may_start_lam_alef = true;
  332. } else {
  333. if (stt->may_start_lam_alef
  334. && (visual ? IS_LAM_N(ch) : IS_ALEF_N(ch))) {
  335. // yes, this is the second character of the ligature.
  336. // return 0 (the ligature width is 1, and the first
  337. // character already returned 1).
  338. stt->may_start_lam_alef = false;
  339. return 0;
  340. } else {
  341. if (width != 0 || !is_shaping_transparent(ch))
  342. stt->may_start_lam_alef = false;
  343. }
  344. }
  345. }
  346. if (width != 1) {
  347. if (ch == '\t') {
  348. return (tab_width - (pos % tab_width));
  349. } else if (width == -1 || ch == '\0') {
  350. // We print control characters and nulls on one column, so
  351. // we set their width to 1.
  352. width = 1;
  353. } else if (width == 0) {
  354. // Calculate required width for BIDI codes and Hebrew points
  355. // based on user's preferences.
  356. if (BiDi::is_explicit_mark(ch)) {
  357. if (show_explicits)
  358. width = 1;
  359. } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
  360. if (rtl_nsm_display == rtlnsmTransliterated)
  361. width = 1;
  362. } else {
  363. // Now handle general NSMs:
  364. // if it's not a UTF-8 terminal, assume it can't combine
  365. // characters. Same for fixed terminals.
  366. if (!terminal::is_utf8 || terminal::is_fixed)
  367. width = 1;
  368. }
  369. } else {
  370. // width == 2, Asian ideographs
  371. if (!terminal::is_utf8 || terminal::is_fixed)
  372. width = 1;
  373. }
  374. }
  375. return width;
  376. }
  377. int EditBox::get_str_width(const unichar *str, idx_t len, bool visual)
  378. {
  379. wdstate stt;
  380. int width = 0;
  381. while (len--)
  382. width += get_char_width(*str++, width, &stt, true);
  383. return width;
  384. }
  385. // get_rev_str_width() - sums the widths of the characters starting at the
  386. // end of the string. it makes a difference only for the TAB character.
  387. int EditBox::get_rev_str_width(const unichar *str, idx_t len)
  388. {
  389. wdstate stt;
  390. int width = 0;
  391. str += len;
  392. while (len--)
  393. width += get_char_width(*(--str), width, &stt);
  394. return width;
  395. }
  396. // calc_vis_column() - returns the visual column the cursor is at.
  397. // It does a logical-to-visual conversion, then it sums, in visual order,
  398. // the widths of the characters till the cursor.
  399. int EditBox::calc_vis_column()
  400. {
  401. Paragraph &p = *curr_para();
  402. // Find the start and the end of the screen line.
  403. int inner_line = calc_inner_line();
  404. idx_t start = (inner_line > 0)
  405. ? p.line_breaks[inner_line - 1]
  406. : 0;
  407. idx_t end = p.line_breaks[inner_line];
  408. idx_t line_len = end - start;
  409. LevelsArray &levels = cache.levels;
  410. IdxArray &position_L_to_V = cache.position_L_to_V;
  411. unistring &vis = cache.vis;
  412. // We apply the BiDi algorithm if the
  413. // results are not already cached.
  414. if (!cache.owned_by(cursor.para)) {
  415. cache.invalidate(); // it's owned by someone else
  416. levels.resize(p.str.len());
  417. DBG(100, ("get_embedding_levels() - by calc_vis_column()\n"));
  418. BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
  419. p.base_dir(), levels.begin(),
  420. p.breaks_count(), p.line_breaks.begin(),
  421. !bidi_enabled);
  422. vis = p.str;
  423. position_L_to_V.resize(p.str.len());
  424. // reorder just the screen line.
  425. reorder(levels.begin() + start, line_len,
  426. NULL, position_L_to_V.begin() + start, vis.begin() + start);
  427. }
  428. int cursor_log_line_pos = cursor.pos - start;
  429. // Note that we use the term "width" to make it clear that we
  430. // sum the widths of the characters. "column", in the name of
  431. // this method, refers to the terminal column.
  432. int cursor_vis_width = 0;
  433. if (cursor_log_line_pos >= line_len) {
  434. // cursor stands at end of line. calculate the
  435. // width of the whole line.
  436. if (p.is_rtl())
  437. cursor_vis_width = get_rev_str_width(vis.begin() + start,
  438. line_len);
  439. else
  440. cursor_vis_width = get_str_width(vis.begin() + start,
  441. line_len);
  442. }
  443. else {
  444. // The cursor is inside the line; find the visual position of the
  445. // cursor, then calculate the width of the segment.
  446. int cursor_vis_line_pos = position_L_to_V[start + cursor_log_line_pos];
  447. if (p.is_rtl())
  448. cursor_vis_width = get_rev_str_width(
  449. vis.begin() + start + cursor_vis_line_pos + 1,
  450. line_len - cursor_vis_line_pos - 1);
  451. else
  452. cursor_vis_width = get_str_width(
  453. vis.begin() + start,
  454. cursor_vis_line_pos);
  455. }
  456. return cursor_vis_width;
  457. }
  458. // move_to_vis_column() - positions the cursor at a visual column.
  459. // It does a visual-to-logical conversion and then sums the widths of the
  460. // characters, in visual order, till it reachs the right column. then
  461. // it converts this visual column to a logical one.
  462. void EditBox::move_to_vis_column(int column)
  463. {
  464. Paragraph &p = *curr_para();
  465. // Find the start and the end of the screen line.
  466. int inner_line = calc_inner_line();
  467. idx_t start = (inner_line > 0)
  468. ? p.line_breaks[inner_line - 1]
  469. : 0;
  470. idx_t end = p.line_breaks[inner_line];
  471. idx_t line_len = end - start;
  472. LevelsArray &levels = cache.levels;
  473. IdxArray &position_V_to_L = cache.position_V_to_L;
  474. unistring &vis = cache.vis;
  475. // We apply the BiDi algorithm if the
  476. // results are not already cached.
  477. if (!cache.owned_by(cursor.para)) {
  478. cache.invalidate(); // it's owned by someone else
  479. levels.resize(p.str.len());
  480. DBG(100, ("get_embedding_levels() - by move_to_vis_column()\n"));
  481. BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
  482. p.base_dir(), levels.begin(),
  483. p.breaks_count(), p.line_breaks.begin(),
  484. !bidi_enabled);
  485. vis = p.str;
  486. position_V_to_L.resize(p.str.len());
  487. // reorder just the screen line.
  488. reorder(levels.begin() + start, line_len,
  489. position_V_to_L.begin() + start, NULL, vis.begin() + start);
  490. }
  491. if (p.is_rtl()) {
  492. // revrse the visual string so that we can use the same loop
  493. // for both RTL and LTR lines.
  494. reverse(vis.begin() + start, line_len);
  495. }
  496. // sum the widths of the charaters, in visual order, till we reach
  497. // the right [visual] column.
  498. int cursor_vis_line_pos = 0; // silence the compiler
  499. int width = 0;
  500. idx_t i;
  501. wdstate stt;
  502. for (i = 0; i < line_len; i++) {
  503. width += get_char_width(vis[start + i], width, &stt, !p.is_rtl());
  504. if (width > column) {
  505. cursor_vis_line_pos = i;
  506. break;
  507. }
  508. }
  509. if (p.is_rtl()) {
  510. // cancel the reverse() we did, because the cache is reused latter.
  511. reverse(vis.begin() + start, line_len);
  512. }
  513. // find the logical column corresponding to the visual column.
  514. int cursor_log_line_pos;
  515. if (i == line_len) { // cursor at end of line?
  516. if (inner_line == p.breaks_count() - 1) { // the last inner line?
  517. // yes, stand past end of line
  518. cursor_log_line_pos = cursor_vis_line_pos = line_len;
  519. } else {
  520. // no, stand on the last char of the line
  521. cursor_log_line_pos = cursor_vis_line_pos = line_len - 1;
  522. }
  523. } else {
  524. // use the V_to_L mapping to find the logical column.
  525. if (p.is_rtl()) {
  526. cursor_vis_line_pos = line_len - cursor_vis_line_pos - 1;
  527. cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
  528. } else {
  529. cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
  530. }
  531. }
  532. cursor.pos = start + cursor_log_line_pos;
  533. }
  534. int EditBox::get_text_width() const
  535. {
  536. if (!terminal::is_interactive())
  537. return non_interactive_text_width;
  538. else
  539. return window_width() - margin_before - margin_after;
  540. }
  541. // wrap_para() - wraps a paragraph. that means to populate the line_breaks
  542. // array. this method is called when a paragraph has changed or when various
  543. // display options that affect the width of some characters are changed.
  544. void EditBox::wrap_para(Paragraph &para)
  545. {
  546. para.line_breaks.clear();
  547. if (wrap_type == wrpOff) {
  548. para.line_breaks.push_back(para.str.len());
  549. return;
  550. }
  551. int visible_text_width = get_text_width();
  552. int line_width = 0;
  553. idx_t line_len = 0;
  554. wdstate stt;
  555. for (idx_t i = 0; i < para.str.len(); i++) {
  556. int char_width = get_char_width(para.str[i], line_width, &stt);
  557. if ( (line_width + char_width > visible_text_width && line_len > 0)
  558. || para.str[i] == UNICODE_LS )
  559. {
  560. if (para.str[i] == UNICODE_LS) {
  561. ; // do nothing: break after LS; don't trace back to wspace
  562. } else if (wrap_type == wrpAtWhiteSpace) {
  563. // avoid breaking words: break at the previous wspace
  564. idx_t saved_i = i;
  565. while (line_len > 0
  566. && (!BiDi::is_space(para.str[i])
  567. || i == para.str.len() - 1)) {
  568. i--;
  569. line_len--;
  570. }
  571. if (line_len == 0) {
  572. // no wspace found; we have to break the word
  573. i = saved_i - 1;
  574. }
  575. } else { // wrpAnywhere
  576. i--;
  577. }
  578. para.line_breaks.push_back(i + 1);
  579. line_len = 0;
  580. line_width = 0;
  581. stt.clear();
  582. } else {
  583. line_len++;
  584. line_width += char_width;
  585. }
  586. }
  587. // add the end-of-paragraph to line_breaks.
  588. // first make sure it's not already there (e.g. when the paragraph
  589. // terminates in a LS).
  590. if (para.line_breaks.empty() || para.line_breaks.back() != para.str.len())
  591. para.line_breaks.push_back(para.str.len());
  592. }
  593. void EditBox::rewrap_all()
  594. {
  595. for (int i = 0; i < parags_count(); i++)
  596. wrap_para(*paragraphs[i]);
  597. if (wrap_type == wrpOff) {
  598. // if we've just turned wrap off, make sure the top inner line is 0.
  599. top_line.inner_line = 0;
  600. }
  601. scroll_to_cursor_line();
  602. request_update(rgnAll);
  603. }
  604. void EditBox::reformat()
  605. {
  606. rewrap_all();
  607. }
  608. void EditBox::set_wrap_type(WrapType value)
  609. {
  610. wrap_type = value;
  611. rewrap_all();
  612. NOTIFY_CHANGE(wrap);
  613. }
  614. INTERACTIVE void EditBox::toggle_wrap()
  615. {
  616. switch (wrap_type) {
  617. case wrpOff: set_wrap_type(wrpAtWhiteSpace); break;
  618. case wrpAtWhiteSpace: set_wrap_type(wrpAnywhere); break;
  619. case wrpAnywhere: set_wrap_type(wrpOff); break;
  620. }
  621. }
  622. void EditBox::set_tab_width(int value)
  623. {
  624. if (value > 0) {
  625. tab_width = value;
  626. rewrap_all();
  627. }
  628. }
  629. void EditBox::resize(int lines, int columns, int y, int x)
  630. {
  631. Widget::resize(lines, columns, y, x);
  632. if (old_width != columns) { // has the width changed?
  633. rewrap_all();
  634. } else {
  635. // No, no need to rewrap
  636. scroll_to_cursor_line();
  637. request_update(rgnAll);
  638. }
  639. old_width = columns;
  640. }
  641. // }}}
  642. // Syntax Highlighting {{{
  643. void EditBox::set_syn_hlt(syn_hlt_t syn)
  644. {
  645. syn_hlt = syn;
  646. request_update(rgnAll);
  647. }
  648. INTERACTIVE void EditBox::menu_set_syn_hlt_none()
  649. {
  650. set_syn_hlt(synhltOff);
  651. }
  652. INTERACTIVE void EditBox::menu_set_syn_hlt_html()
  653. {
  654. set_syn_hlt(synhltHTML);
  655. }
  656. INTERACTIVE void EditBox::menu_set_syn_hlt_email()
  657. {
  658. set_syn_hlt(synhltEmail);
  659. }
  660. void EditBox::set_underline(bool v)
  661. {
  662. underline_hlt = v;
  663. request_update(rgnAll);
  664. }
  665. INTERACTIVE void EditBox::toggle_underline()
  666. {
  667. set_underline(!get_underline());
  668. }
  669. // detect_syntax() tries to detect the syntax of the buffer (and turn on
  670. // the appropriate highlighting). If 2 or more lines starting with ">"
  671. // are found within the first 10 lines, it's assumed to be an email
  672. // message. If "<html" or "<HTML" is found within the first 5 lines, it's
  673. // assumed to be HTML.
  674. void EditBox::detect_syntax()
  675. {
  676. syn_hlt_t syntax = synhltOff;
  677. int quote_count = 0;
  678. for (int i = 0; i < 10 && i < parags_count(); i++) {
  679. const unistring &str = paragraphs[i]->str;
  680. idx_t len = str.len();
  681. idx_t pos = 0;
  682. while (pos < len && str[pos] == ' ')
  683. pos++;
  684. if (pos < len && str[pos] == '>')
  685. quote_count++;
  686. }
  687. if (quote_count >= 2) {
  688. syntax = synhltEmail;
  689. } else {
  690. for (int i = 0; i < 5 && i < parags_count(); i++) {
  691. if ((paragraphs[i]->str.index(u8string("<html")) != -1) ||
  692. (paragraphs[i]->str.index(u8string("<HTML")) != -1))
  693. {
  694. syntax = synhltHTML;
  695. break;
  696. }
  697. }
  698. }
  699. set_syn_hlt(syntax);
  700. }
  701. // Highlight HTML
  702. static void highlight_html(const unistring &str, EditBox::AttributeArray& attributes)
  703. {
  704. idx_t len = str.len();
  705. attribute_t html_attr = get_attr(EDIT_HTML_TAG_ATTR);
  706. bool is_color = contains_color(html_attr);
  707. bool in_tag = false;
  708. for (idx_t pos = 0; pos < len; pos++) {
  709. if (str[pos] == '<')
  710. in_tag = true;
  711. if (in_tag) {
  712. if (is_color)
  713. attributes[pos] = html_attr;
  714. else
  715. attributes[pos] |= html_attr;
  716. }
  717. if (str[pos] == '>')
  718. in_tag = false;
  719. }
  720. }
  721. // Highlight Email
  722. #define MAX_QUOTE_LEVEL 9
  723. static void highlight_email(const unistring &str, EditBox::AttributeArray& attributes)
  724. {
  725. idx_t len = str.len();
  726. attribute_t quote_attr = A_NORMAL; // silence the compiler.
  727. bool is_color = false;
  728. idx_t pos = 0;
  729. int level = 0;
  730. while (pos < len && str[pos] == ' ')
  731. pos++;
  732. if (pos < len && str[pos] == '>')
  733. while (pos < len) {
  734. if (str[pos] == '>' && level < MAX_QUOTE_LEVEL) {
  735. level++;
  736. quote_attr = get_attr(EDIT_EMAIL_QUOTE1_ATTR + level - 1);
  737. is_color = contains_color(quote_attr);
  738. }
  739. if (is_color)
  740. attributes[pos] = quote_attr;
  741. else
  742. attributes[pos] |= quote_attr;
  743. pos++;
  744. }
  745. }
  746. inline bool is_ltr_alpha(unichar ch)
  747. {
  748. return BiDi::is_alnum(ch) && !BiDi::is_rtl(ch);
  749. }
  750. // Highlight underline (*text* and _text_)
  751. static void highlight_underline(const unistring &str, EditBox::AttributeArray& attributes)
  752. {
  753. idx_t len = str.len();
  754. attribute_t underline_attr = get_attr(EDIT_EMPHASIZED_ATTR);
  755. bool is_color = contains_color(underline_attr);
  756. bool in_emph = false;
  757. for (idx_t pos = 0; pos < len; pos++) {
  758. bool emph_ch = (str[pos] == '_' || str[pos] == '*');
  759. if (emph_ch) {
  760. // ignore "http://host/show_bug.cgi" and "ticks_per_second" by making
  761. // sure it's not LTR alphanumerics on both sides.
  762. if (pos > 0 && is_ltr_alpha(str[pos-1]) &&
  763. pos < len-1 && is_ltr_alpha(str[pos+1]))
  764. emph_ch = false;
  765. }
  766. if (emph_ch)
  767. in_emph = !in_emph;
  768. if (in_emph || emph_ch) {
  769. if (is_color)
  770. attributes[pos] = underline_attr;
  771. else
  772. attributes[pos] |= underline_attr;
  773. }
  774. }
  775. }
  776. // }}}
  777. // low-level drawing {{{
  778. // get_char_repr() - get representation of some undisplayable characters,
  779. // EOPs, and of some interface elements (e.g. '$').
  780. unichar EditBox::get_char_repr(unichar ch)
  781. {
  782. if (reprtbl.translate_char(ch)) {
  783. return ch;
  784. } else {
  785. if (reprtbl.empty())
  786. return 'X';
  787. else
  788. return ch;
  789. }
  790. }
  791. // draw_unistr() - draws an LTR visual string.
  792. // latin1_transliterate() - when not in UTF-8 locale, we transliterate
  793. // some Unicode characters to make them readable.
  794. static unichar latin1_transliterate(unichar ch)
  795. {
  796. switch (ch) {
  797. // Note: 0xB4 (SPACING ACCENT) and 0xA8 (SPACING DIARESIS) cause
  798. // some troubles on my Linux console when not in UTF-8 locale, probably
  799. // because the console driver erroneously thinks it's a non-spacing
  800. // mark (as U+0301 and U+0308 are).
  801. case 0xB4:
  802. case 0xA8:
  803. return 'x';
  804. case 0x203E:
  805. return 0xAF; // OVERLINE (we transliterate to "macron" because
  806. // it's similar)
  807. case UNI_HEB_MAQAF:
  808. case UNI_HYPHEN:
  809. case UNI_NON_BREAKING_HYPHEN:
  810. case UNI_EN_DASH:
  811. case UNI_EM_DASH:
  812. case UNI_MINUS_SIGN:
  813. return '-';
  814. case UNI_HEB_GERESH:
  815. case UNI_RIGHT_SINGLE_QUOTE:
  816. case UNI_SINGLE_LOW9_QUOTE:
  817. case UNI_SINGLE_HIGH_REV9_QUOTE:
  818. return '\'';
  819. case UNI_LEFT_SINGLE_QUOTE:
  820. return '`';
  821. case UNI_HEB_GERSHAYIM:
  822. case UNI_LEFT_DOUBLE_QUOTE:
  823. case UNI_RIGHT_DOUBLE_QUOTE:
  824. case UNI_DOUBLE_LOW9_QUOTE:
  825. case UNI_DOUBLE_HIGH_REV9_QUOTE:
  826. return '"';
  827. case UNI_HEB_SOF_PASUQ:
  828. return ':';
  829. case UNI_HEB_PASEQ:
  830. return '|';
  831. case UNI_BULLET:
  832. return '*';
  833. }
  834. return ch;
  835. }
  836. void EditBox::draw_unistr(const unichar *str, idx_t len,
  837. attribute_t *attributes, int *tab_widths)
  838. {
  839. unistring buf;
  840. if (terminal::do_arabic_shaping) {
  841. buf.append(str, len);
  842. len = shape(buf.begin(), len, attributes);
  843. buf.resize(len);
  844. str = buf.begin();
  845. }
  846. #define SETWATTR(_attr) \
  847. if (current_attr != int(_attr)) { \
  848. wattrset(wnd, _attr); \
  849. current_attr = _attr; \
  850. }
  851. #ifdef HAVE_WIDE_CURSES
  852. #define put_unichar_attr(_wch, _attr) \
  853. do { \
  854. unichar wch = _wch; \
  855. if (!terminal::is_utf8) { \
  856. wch = latin1_transliterate(wch); \
  857. if (WCTOB(wch) == EOF) { \
  858. wch = get_char_repr(FAILED_CONV_REPR); \
  859. /*def_attr |= FAILED_CONV_COLOR;*/ \
  860. if (contains_color(def_attr)) \
  861. def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
  862. else \
  863. def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
  864. } \
  865. } \
  866. /*SETWATTR(_attr | def_attr);*/ \
  867. SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
  868. waddnwstr(wnd, (wchar_t*)&wch, 1); \
  869. } while (0)
  870. #else
  871. #define put_unichar_attr(_wch, _attr) \
  872. do { \
  873. int ich = latin1_transliterate(_wch); \
  874. ich = terminal::force_iso88598 ? unicode_to_iso88598(ich) : WCTOB(ich); \
  875. if (ich == EOF) { \
  876. ich = get_char_repr(FAILED_CONV_REPR); \
  877. /*def_attr |= FAILED_CONV_COLOR;*/ \
  878. if (contains_color(def_attr)) \
  879. def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
  880. else \
  881. def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
  882. } \
  883. /*SETWATTR(_attr | def_attr);*/ \
  884. SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
  885. waddch(wnd, (unsigned char)ich); \
  886. } while (0)
  887. #endif
  888. int tab_counter = 0;
  889. int line_width = 0;
  890. int current_attr = -1;
  891. for (idx_t i = 0; i < len; i++) {
  892. unichar ch = str[i];
  893. int char_width = IS_SIMPLE_ONE_COLUMN_CHAR(ch) ? 1 : mk_wcwidth(ch);
  894. int def_attr = attributes ? attributes[i] : A_NORMAL;
  895. if (char_width < 1) {
  896. if (ch == '\t') {
  897. if (tab_widths)
  898. char_width = tab_widths[tab_counter++];
  899. else
  900. char_width = tab_width - (line_width % tab_width);
  901. for (int t = 0; t < char_width; t++) {
  902. if (show_tabs)
  903. put_unichar_attr(get_char_repr('\t'), get_attr(EDIT_TAB_ATTR));
  904. else
  905. put_unichar_attr(' ', 0);
  906. }
  907. } else if (char_width == -1 || ch == '\0') {
  908. // Control characters / nulls: print symbol instead
  909. char_width = 1;
  910. put_unichar_attr(get_char_repr(CONTROL_REPR), get_attr(EDIT_CONTROL_ATTR));
  911. } else {
  912. // Non-Spacing Marks.
  913. // First, handle BIDI explicits and Hebrew NSMs
  914. if (BiDi::is_explicit_mark(ch)) {
  915. if (show_explicits) {
  916. char_width = 1;
  917. put_unichar_attr(get_char_repr(ch), get_attr(EDIT_EXPLICIT_ATTR));
  918. }
  919. } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
  920. if (rtl_nsm_display == rtlnsmTransliterated) {
  921. char_width = 1;
  922. put_unichar_attr(get_char_repr(ch),
  923. BiDi::is_cantillation_nsm(ch)
  924. ? get_attr(EDIT_NSM_CANTILLATION_ATTR)
  925. : (BiDi::is_arabic_nsm(ch)
  926. ? get_attr(EDIT_NSM_ARABIC_ATTR)
  927. : get_attr(EDIT_NSM_HEBREW_ATTR)) );
  928. } else if (rtl_nsm_display == rtlnsmAsis
  929. && terminal::is_utf8 && !terminal::is_fixed) {
  930. put_unichar_attr(ch, 0);
  931. }
  932. } else {
  933. // Now handle general NSMs
  934. if (!terminal::is_utf8 || terminal::is_fixed) {
  935. char_width = 1;
  936. put_unichar_attr(get_char_repr(NSM_REPR), get_attr(EDIT_NSM_ATTR));
  937. } else {
  938. // :TODO: new func: is_printable_zw()
  939. if (ch != UNI_ZWNJ && ch != UNI_ZWJ) {
  940. put_unichar_attr(ch, 0);
  941. }
  942. }
  943. }
  944. }
  945. } else {
  946. if (char_width == 2 && (!terminal::is_utf8 || terminal::is_fixed)) {
  947. // Wide Asian ideograph, but terminal is not capable
  948. char_width = 1;
  949. put_unichar_attr(get_char_repr(WIDE_REPR), get_attr(EDIT_WIDE_ATTR));
  950. } else {
  951. attribute_t xattr = 0;
  952. if (ch == UNICODE_LS) {
  953. ch = get_char_repr(UNICODE_LS);
  954. xattr = get_attr(EDIT_UNICODE_LS_ATTR);
  955. } else if (ch == UNI_NO_BREAK_SPACE) {
  956. ch = ' ';
  957. } else if (ch == UNI_HEB_MAQAF) {
  958. if (maqaf_display == mqfTransliterated
  959. || maqaf_display == mqfHighlighted)
  960. ch = get_char_repr(UNI_HEB_MAQAF);
  961. if (maqaf_display == mqfHighlighted)
  962. xattr = get_attr(EDIT_MAQAF_ATTR);
  963. }
  964. put_unichar_attr(ch, xattr);
  965. }
  966. }
  967. line_width += char_width;
  968. }
  969. SETWATTR(A_NORMAL);
  970. #undef SETWATTR
  971. #undef put_unichar_attr
  972. }
  973. void EditBox::calc_tab_widths(const unichar *str, idx_t len,
  974. bool rev, IntArray &tab_widths)
  975. {
  976. tab_widths.clear();
  977. int line_width = 0;
  978. wdstate stt;
  979. idx_t i = rev ? len - 1 : 0;
  980. while (len--) {
  981. int char_width = get_char_width(str[i], line_width, &stt);
  982. if (str[i] == '\t') {
  983. if (rev)
  984. tab_widths.insert(tab_widths.begin(), char_width);
  985. else
  986. tab_widths.push_back(char_width);
  987. }
  988. line_width += char_width;
  989. i += rev ? -1 : 1;
  990. }
  991. }
  992. // draw_rev_unistr() - draws an RTL visual string. it calls draw_string()
  993. // to do the actuall work. The two functions differ in how they
  994. // calculate TAB widths. draw_rev_unistr() is neccessary because in RTL
  995. // visual strings the first column is at the end of the string. it
  996. // calculates the TAB widths accordingly and passes them to draw_unistring().
  997. void EditBox::draw_rev_unistr(const unichar *str, idx_t len,
  998. attribute_t *attributes)
  999. {
  1000. IntArray tab_widths;
  1001. calc_tab_widths(str, len, true, tab_widths);
  1002. draw_unistr(str, len, attributes, tab_widths.begin());
  1003. }
  1004. void EditBox::put_unichar_attr_at(int line, int col, unichar ch, int attr)
  1005. {
  1006. wattrset(wnd, attr);
  1007. wmove(wnd, line, col);
  1008. put_unichar(ch, get_char_repr(FAILED_CONV_REPR));
  1009. }
  1010. // draw_eop() - draws an EOP symbol.
  1011. void EditBox::draw_eop(int y, int x, Paragraph &p, bool selected)
  1012. {
  1013. if (!show_paragraph_endings)
  1014. return;
  1015. unichar ch;
  1016. switch (p.eop) {
  1017. case eopMac: ch = EOP_MAC_REPR; break;
  1018. case eopDOS: ch = EOP_DOS_REPR; break;
  1019. case eopUnix: ch = p.is_rtl()
  1020. ? EOP_UNIX_RTL_REPR
  1021. : EOP_UNIX_LTR_REPR;
  1022. break;
  1023. case eopUnicode:
  1024. ch = EOP_UNICODE_REPR; break;
  1025. default:
  1026. ch = EOP_NONE_REPR;
  1027. }
  1028. put_unichar_attr_at(y, x, get_char_repr(ch),
  1029. get_attr(EDIT_EOP_ATTR) | (selected ? A_REVERSE : 0));
  1030. }
  1031. // }}}
  1032. // redraw_paragraph {{{
  1033. void EditBox::redraw_unwrapped_paragraph(
  1034. Paragraph &p,
  1035. int window_start_line,
  1036. bool only_cursor,
  1037. int para_num,
  1038. LevelsArray &levels,
  1039. IdxArray& position_L_to_V,
  1040. IdxArray& position_V_to_L,
  1041. AttributeArray& attributes,
  1042. bool eop_is_selected
  1043. )
  1044. {
  1045. unistring &vis = cache.vis;
  1046. if (!cache.owned_by(para_num)) {
  1047. vis = p.str;
  1048. reorder(levels.begin(), p.str.len(),
  1049. position_V_to_L.begin(),
  1050. position_L_to_V.begin(),
  1051. vis.begin(),
  1052. attributes.begin(), do_mirror,
  1053. (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
  1054. }
  1055. // Step 1. find out the start and end of the
  1056. // segment visible on screen.
  1057. // note: we use the term "col", or "pos", to refer to character index, and
  1058. // "width" to refer to the visual position on screen -- sum of the widths
  1059. // of the preceeding character.
  1060. // find the visual cursor index
  1061. int cursor_vis_width = 0;
  1062. idx_t cursor_vis_line_pos = -1;
  1063. if (curr_para() == &p) {
  1064. if (cursor.pos == p.str.len())
  1065. cursor_vis_line_pos = p.str.len();
  1066. else {
  1067. cursor_vis_line_pos = position_L_to_V[cursor.pos];
  1068. if (p.is_rtl())
  1069. cursor_vis_line_pos = p.str.len() - cursor_vis_line_pos - 1;
  1070. }
  1071. }
  1072. idx_t start_col = 0, end_col = 0;
  1073. int visible_text_width = get_text_width();
  1074. int segment_width = 0;
  1075. if (p.is_rtl()) {
  1076. reverse(vis.begin(), p.str.len());
  1077. reverse(attributes.begin(), p.str.len());
  1078. }
  1079. idx_t i;
  1080. wdstate stt;
  1081. for (i = 0; i < p.str.len(); i++) {
  1082. int char_width = get_char_width(vis[i], segment_width, &stt, !p.is_rtl());
  1083. segment_width += char_width;
  1084. if (segment_width > visible_text_width) {
  1085. if (cursor_vis_line_pos < i) {
  1086. // we've found the end of the segment.
  1087. segment_width -= char_width;
  1088. end_col = i;
  1089. break;
  1090. } else {
  1091. // start a new segment. recalculate the width of this
  1092. // first character because TAB's width may change.
  1093. stt.clear();
  1094. char_width = get_char_width(vis[i], 0, &stt, !p.is_rtl());
  1095. segment_width = char_width;
  1096. start_col = i;
  1097. }
  1098. }
  1099. if (i == cursor_vis_line_pos)
  1100. cursor_vis_width = segment_width - char_width;
  1101. }
  1102. if (end_col == 0)
  1103. end_col = i;
  1104. if (cursor.pos == p.str.len()) {
  1105. cursor_vis_width = segment_width;
  1106. }
  1107. if (p.is_rtl()) {
  1108. reverse(vis.begin() + start_col, end_col - start_col);
  1109. reverse(attributes.begin() + start_col, end_col - start_col);
  1110. }
  1111. // Step 2. draw the segment [start_col .. end_col)
  1112. if (p.is_rtl()) {
  1113. wmove(wnd, window_start_line,
  1114. margin_after + visible_text_width - segment_width);
  1115. draw_rev_unistr(vis.begin() + start_col, end_col - start_col,
  1116. attributes.begin() + start_col);
  1117. } else {
  1118. wmove(wnd, window_start_line, margin_before);
  1119. draw_unistr(vis.begin() + start_col, end_col - start_col,
  1120. attributes.begin() + start_col);
  1121. }
  1122. if (p.is_rtl()) {
  1123. // cancel the reverse() we did, because the cache is reused latter.
  1124. reverse(vis.begin() + start_col, end_col - start_col);
  1125. reverse(vis.begin(), p.str.len());
  1126. }
  1127. // Step 3. draw EOP / continuation indicator
  1128. if (end_col != p.str.len()) {
  1129. // if the end of the para is not shown,
  1130. // draw line-countinuation indicator ('$')
  1131. put_unichar_attr_at(
  1132. window_start_line,
  1133. p.is_rtl() ? margin_after - 1
  1134. : margin_before + visible_text_width,
  1135. get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
  1136. } else {
  1137. // if the end is shown, draw EOP
  1138. draw_eop(window_start_line,
  1139. p.is_rtl() ? margin_after + visible_text_width
  1140. - segment_width - 1
  1141. : margin_before + segment_width,
  1142. p, eop_is_selected);
  1143. }
  1144. if (start_col != 0 && cursor_vis_width > 2) {
  1145. // if the beginning of the para is not shown,
  1146. // draw another line-continuation indicator, at the other side.
  1147. put_unichar_attr_at(
  1148. window_start_line,
  1149. p.is_rtl() ? margin_after + visible_text_width - 1
  1150. : margin_before,
  1151. get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
  1152. }
  1153. // Step 4. position the cursor
  1154. if (p.is_rtl()) {
  1155. cursor_vis_width = visible_text_width - cursor_vis_width - 1;
  1156. wmove(wnd, window_start_line, margin_after + cursor_vis_width);
  1157. } else {
  1158. wmove(wnd, window_start_line, margin_before + cursor_vis_width);
  1159. }
  1160. }
  1161. void EditBox::redraw_wrapped_paragraph(
  1162. Paragraph &p,
  1163. int window_start_line,
  1164. bool only_cursor,
  1165. int para_num,
  1166. LevelsArray &levels,
  1167. IdxArray& position_L_to_V,
  1168. IdxArray& position_V_to_L,
  1169. AttributeArray& attributes,
  1170. bool eop_is_selected
  1171. )
  1172. {
  1173. idx_t cursor_vis_line_pos = -1;
  1174. int cursor_vis_width = 0;
  1175. int cursor_line = -1;
  1176. unistring &vis = cache.vis;
  1177. if (!cache.owned_by(para_num))
  1178. vis = p.str;
  1179. int visible_text_width = get_text_width();
  1180. idx_t prev_line_break = 0;
  1181. // draw the paragraph line by line.
  1182. for (int line_num = 0;
  1183. line_num < p.breaks_count();
  1184. line_num++)
  1185. {
  1186. idx_t line_break = p.line_breaks[line_num];
  1187. idx_t line_len = line_break - prev_line_break;
  1188. bool is_last_line = (line_num == p.breaks_count() - 1);
  1189. if (!cache.owned_by(para_num)) {
  1190. reorder(levels.begin() + prev_line_break, line_len,
  1191. position_V_to_L.begin() + prev_line_break,
  1192. position_L_to_V.begin() + prev_line_break,
  1193. vis.begin() + prev_line_break,
  1194. attributes.begin() + prev_line_break, do_mirror,
  1195. (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
  1196. }
  1197. // draw segment [prev_line_break .. line_break)
  1198. if ( !only_cursor && window_start_line + line_num >= 0
  1199. && window_start_line + line_num < window_height() ) {
  1200. // :TODO: handle TABS at end of line (especially RTL lines)
  1201. if (p.is_rtl()) {
  1202. int line_width = get_rev_str_width(
  1203. vis.begin() + prev_line_break, line_len);
  1204. int window_x = margin_after;
  1205. // we reserve one space for wrap (margin_after), so:
  1206. if (line_width > visible_text_width)
  1207. window_x--;
  1208. else
  1209. window_x += visible_text_width - line_width;
  1210. wmove(wnd, window_start_line + line_num, window_x);
  1211. draw_rev_unistr(vis.begin() + prev_line_break, line_len,
  1212. attributes.begin() + prev_line_break);
  1213. if (is_last_line) {
  1214. draw_eop(window_start_line + line_num, window_x - 1,
  1215. p, eop_is_selected);
  1216. } else if (wrap_type == wrpAnywhere) {
  1217. put_unichar_attr_at(
  1218. window_start_line + line_num,
  1219. margin_after - 1,
  1220. get_char_repr(WRAP_RTL_REPR), get_attr(EDIT_WRAP_ATTR));
  1221. }
  1222. } else {
  1223. wmove(wnd, window_start_line + line_num, margin_before);
  1224. draw_unistr(vis.begin() + prev_line_break, line_len,
  1225. attributes.begin() + prev_line_break);
  1226. if (is_last_line) {
  1227. draw_eop(window_start_line + line_num,
  1228. margin_before + get_str_width(vis.begin()
  1229. + prev_line_break, line_len),
  1230. p, eop_is_selected);
  1231. } else if (wrap_type == wrpAnywhere) {
  1232. put_unichar_attr_at(
  1233. window_start_line + line_num,
  1234. margin_before + visible_text_width,
  1235. get_char_repr(WRAP_LTR_REPR), get_attr(EDIT_WRAP_ATTR));
  1236. }
  1237. }
  1238. }
  1239. // find the visual cursor position
  1240. if (curr_para() == &p && cursor_line == -1) {
  1241. if (cursor.pos < line_break) {
  1242. int cursor_log_line_pos = cursor.pos - prev_line_break;
  1243. cursor_vis_line_pos = position_L_to_V[
  1244. prev_line_break + cursor_log_line_pos];
  1245. if (p.is_rtl()) {
  1246. cursor_vis_width = get_rev_str_width(
  1247. vis.begin() + prev_line_break
  1248. + cursor_vis_line_pos + 1,
  1249. line_len - cursor_vis_line_pos - 1);
  1250. } else {
  1251. cursor_vis_width = get_str_width(
  1252. vis.begin() + prev_line_break,
  1253. cursor_vis_line_pos);
  1254. }
  1255. cursor_line = line_num;
  1256. }
  1257. else if (cursor.pos == p.str.len() && is_last_line) {
  1258. if (p.is_rtl()) {
  1259. cursor_vis_width = get_rev_str_width(
  1260. vis.begin() + prev_line_break,
  1261. line_len);
  1262. } else {
  1263. cursor_vis_width = get_str_width(
  1264. vis.begin() + prev_line_break,
  1265. line_len);
  1266. }
  1267. cursor_line = line_num;
  1268. }
  1269. }
  1270. prev_line_break = line_break;
  1271. }
  1272. // position the cursor
  1273. if (cursor_line != -1) {
  1274. if (p.is_rtl()) {
  1275. cursor_vis_width = visible_text_width - cursor_vis_width - 1;
  1276. wmove(wnd,
  1277. window_start_line + cursor_line,
  1278. margin_after + cursor_vis_width);
  1279. } else {
  1280. wmove(wnd,
  1281. window_start_line + cursor_line,
  1282. margin_before + cursor_vis_width);
  1283. }
  1284. }
  1285. }
  1286. void EditBox::do_syntax_highlight(const unistring &str,
  1287. AttributeArray &attributes, int para_num)
  1288. {
  1289. switch (syn_hlt) {
  1290. case synhltHTML:
  1291. highlight_html(str, attributes);
  1292. break;
  1293. case synhltEmail:
  1294. highlight_email(str, attributes);
  1295. break;
  1296. default:
  1297. break;
  1298. }
  1299. if (underline_hlt)
  1300. highlight_underline(str, attributes);
  1301. }
  1302. void EditBox::redraw_paragraph(Paragraph &p, int window_start_line,
  1303. bool only_cursor, int para_num)
  1304. {
  1305. //if (is_primary_mark_set()
  1306. //cache.invalidate();
  1307. if (is_primary_mark_set()
  1308. || wrap_type == wrpOff) // FIXME: when wrap=off, highlighting fucks up in RTL lines;
  1309. // cache.invalidate() is a temporary solution.
  1310. cache.invalidate();
  1311. LevelsArray &levels = cache.levels;
  1312. IdxArray &position_L_to_V = cache.position_L_to_V;
  1313. IdxArray &position_V_to_L = cache.position_V_to_L;
  1314. AttributeArray &attributes = cache.attributes;
  1315. if (!cache.owned_by(para_num)) {
  1316. position_L_to_V.resize(p.str.len());
  1317. position_V_to_L.resize(p.str.len());
  1318. levels.resize(p.str.len());
  1319. attributes.clear();
  1320. attributes.resize(p.str.len());
  1321. DBG(100, ("get_embedding_levels() - by redraw_paragraph()\n"));
  1322. BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
  1323. p.base_dir(), levels.begin(),
  1324. p.breaks_count(), p.line_breaks.begin(),
  1325. !bidi_enabled);
  1326. do_syntax_highlight(p.str, attributes, para_num);
  1327. }
  1328. //AttributeArray attributes(p.str.len());
  1329. bool eop_is_selected = false;
  1330. // We use the "attributes" array to highlight the selection.
  1331. // for each selected characetr, we "OR" the corresponding
  1332. // attribute element with A_REVERSE.
  1333. if (is_primary_mark_set()) {
  1334. Point lo = primary_mark, hi = cursor;
  1335. if (hi < lo)
  1336. hi.swap(lo);
  1337. if (lo.para <= para_num && hi.para >= para_num) {
  1338. idx_t start, end;
  1339. if (lo.para == para_num)
  1340. start = lo.pos;
  1341. else
  1342. start = 0;
  1343. if (hi.para == para_num)
  1344. end = hi.pos;
  1345. else {
  1346. eop_is_selected = true;
  1347. end = p.str.len();
  1348. }
  1349. attribute_t selected_attr = get_attr(EDIT_SELECTED_ATTR);
  1350. idx_t i;
  1351. if (contains_color(selected_attr))
  1352. for (i = start; i < end; i++)
  1353. attributes[i] = selected_attr;
  1354. else
  1355. for (i = start; i < end; i++)
  1356. attributes[i] |= selected_attr;
  1357. }
  1358. }
  1359. if (wrap_type == wrpOff) {
  1360. redraw_unwrapped_paragraph(
  1361. p,
  1362. window_start_line,
  1363. only_cursor,
  1364. para_num,
  1365. levels,
  1366. position_L_to_V,
  1367. position_V_to_L,
  1368. attributes,
  1369. eop_is_selected
  1370. );
  1371. } else {
  1372. redraw_wrapped_paragraph(
  1373. p,
  1374. window_start_line,
  1375. only_cursor,
  1376. para_num,
  1377. levels,
  1378. position_L_to_V,
  1379. position_V_to_L,
  1380. attributes,
  1381. eop_is_selected
  1382. );
  1383. }
  1384. cache.set_owner(para_num);
  1385. }
  1386. // }}}
  1387. // Log2Vis {{{
  1388. // emph_string() - emphasize marked-up segments. I.e. converts
  1389. // "the *quick* fox" to "the q_u_i_c_k_ fox". but "* bullet"
  1390. // stays "* bullet".
  1391. static void emph_string(unistring &str,
  1392. unichar emph_marker, unichar emph_ch)
  1393. {
  1394. bool in_emph = false;
  1395. for (int i = 0; i < str.len(); i++) {
  1396. if ( (!emph_marker && (str[i] == '*' || str[i] == '_')) ||
  1397. ( emph_marker && str[i] == emph_marker ) ) {
  1398. if (!(!in_emph &&
  1399. i < str.len()-1 &&
  1400. BiDi::is_space(str[i+1])) )
  1401. {
  1402. in_emph = !in_emph;
  1403. str.erase(str.begin() + i);
  1404. i--;
  1405. }
  1406. } else {
  1407. if (in_emph) {
  1408. i++;
  1409. // skip all NSMs.
  1410. while (i < str.len() && BiDi::is_nsm(str[i]))
  1411. i++;
  1412. str.insert(str.begin()+i, emph_ch);
  1413. }
  1414. }
  1415. }
  1416. }
  1417. // log2vis() - convert the [logical] document into visual.
  1418. void EditBox::log2vis(const char *options)
  1419. {
  1420. bool opt_bdo = false;
  1421. bool opt_nopad = false;
  1422. bool opt_engpad = false;
  1423. unichar opt_emph = false;
  1424. unichar opt_emph_marker = 0;
  1425. unichar opt_emph_ch = UNI_NS_UNDERSCORE;
  1426. // parse the 'options' string.
  1427. if (options) {
  1428. if (strstr(options, "bdo"))
  1429. opt_bdo = true;
  1430. if (strstr(options, "nopad"))
  1431. opt_nopad = true;
  1432. if (strstr(options, "engpad"))
  1433. opt_engpad = true;
  1434. const char *s;
  1435. // FIXME: The following writes on s, which is in the read-only buffer options.
  1436. if (s = strstr(options, "emph")) {
  1437. opt_emph = true;
  1438. if (s[4] == ':') {
  1439. opt_emph_ch = strtol(s + 5, (char**)&s, 0); // FIXME: Brute force. Tzafrir
  1440. if (s && (*s == ':' || *s == ','))
  1441. opt_emph_marker = strtol(s + 1, NULL, 0);
  1442. }
  1443. DBG(1, ("--EMPH-- glyph: U+%04lX, marker: U+%04lX\n",
  1444. (unsigned long)opt_emph_ch,
  1445. (unsigned long)opt_emph_marker));
  1446. }
  1447. }
  1448. std::vector<unistring> visuals;
  1449. for (int i = 0; i < parags_count(); i++)
  1450. {
  1451. Paragraph &p = *paragraphs[i];
  1452. unistring &visp = p.str;
  1453. if (opt_emph) {
  1454. emph_string(visp, opt_emph_marker, opt_emph_ch);
  1455. post_para_modification(p);
  1456. }
  1457. cache.invalidate();
  1458. LevelsArray &levels = cache.levels;
  1459. IdxArray &position_L_to_V = cache.position_L_to_V;
  1460. IdxArray &position_V_to_L = cache.position_V_to_L;
  1461. position_L_to_V.resize(p.str.len());
  1462. position_V_to_L.resize(p.str.len());
  1463. levels.resize(p.str.len());
  1464. DBG(100, ("get_embedding_levels() - by log2vis()\n"));
  1465. BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
  1466. p.base_dir(), levels.begin(),
  1467. p.breaks_count(), p.line_breaks.begin());
  1468. int visible_text_width = get_text_width();
  1469. idx_t prev_line_break = 0;
  1470. for (int line_num = 0;
  1471. line_num < p.breaks_count();
  1472. line_num++)
  1473. {
  1474. idx_t line_break = p.line_breaks[line_num];
  1475. idx_t line_len = line_break - prev_line_break;
  1476. // trim
  1477. while (line_len
  1478. && BiDi::is_space(p.str[prev_line_break + line_len - 1]))
  1479. line_len--;
  1480. // convert to visual
  1481. reorder(levels.begin() + prev_line_break, line_len,
  1482. position_V_to_L.begin() + prev_line_break,
  1483. position_L_to_V.begin() + prev_line_break,
  1484. visp.begin() + prev_line_break,
  1485. NULL, true,
  1486. rtl_nsm_display == rtlnsmAsis);
  1487. if (terminal::do_arabic_shaping)
  1488. line_len = shape(p.str.begin() + prev_line_break,
  1489. line_len, NULL);
  1490. unistring visline;
  1491. // pad RTL lines
  1492. if (p.is_rtl() && !opt_nopad) {
  1493. int line_width = get_rev_str_width(
  1494. visp.begin() + prev_line_break, line_len);
  1495. while (line_width < visible_text_width) {
  1496. visline.push_back(' ');
  1497. line_width++;
  1498. }
  1499. }
  1500. // convert TABs to spaces, and convert/erase some
  1501. // special chars.
  1502. IntArray tab_widths;
  1503. int tab_counter = 0;
  1504. calc_tab_widths(visp.begin() + prev_line_break, line_len,
  1505. p.is_rtl(), tab_widths);
  1506. for (int i = prev_line_break; i < prev_line_break + line_len; i++) {
  1507. if (visp[i] == '\t') {
  1508. int j = tab_widths[tab_counter++];
  1509. while (j--)
  1510. visline.push_back(' ');
  1511. } else if (visp[i] == UNI_HEB_MAQAF
  1512. && maqaf_display != mqfAsis) {
  1513. visline.push_back('-');
  1514. } else {
  1515. if (!BiDi::is_transparent_formatting_code(visp[i]))
  1516. visline.push_back(visp[i]);
  1517. }
  1518. }
  1519. // pad LTR lines
  1520. if (!p.is_rtl() && opt_engpad) {
  1521. int line_width = get_str_width(
  1522. visp.begin() + prev_line_break, line_len);
  1523. while (line_width < visible_text_width) {
  1524. visline.push_back(' ');
  1525. line_width++;
  1526. }
  1527. }
  1528. if (opt_bdo) {
  1529. visline.insert(visline.begin(), UNI_LRO);
  1530. visline.push_back(UNI_PDF);
  1531. }
  1532. visuals.push_back(visline);
  1533. prev_line_break = line_break;
  1534. }
  1535. delete paragraphs[i];
  1536. }
  1537. // replace the logical document with the visual version.
  1538. paragraphs.clear();
  1539. for (int i = 0; i < (int)visuals.size(); i++) {
  1540. Paragraph *p = new Paragraph();
  1541. p->str = visuals[i];
  1542. p->eop = eopUnix;
  1543. post_para_modification(*p);
  1544. // skip the last empty line.
  1545. if (!(i == (int)visuals.size() - 1 && p->str.len() == 0))
  1546. paragraphs.push_back(p);
  1547. }
  1548. undo_stack.clear();
  1549. unset_primary_mark();
  1550. set_modified(true);
  1551. cursor.zero();
  1552. scroll_to_cursor_line();
  1553. request_update(rgnAll);
  1554. }
  1555. // }}}