editbox2.cc 48 KB

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