1 /*
2 ** Copyright 2003-2011, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6 #include "config.h"
7 #include "curseseditmessage.H"
8 #include "fkeytraphandler.H"
9 #include "curses/cursesstatusbar.H"
10 #include "curses/cursesmainscreen.H"
11 #include "curses/cursesmoronize.H"
12 #include "colors.H"
13 #include "gettext.H"
14 #include "htmlentity.h"
15 #include "myserverpromptinfo.H"
16 #include "macros.H"
17 #include "maildir/maildirsearch.h"
18 #include <courier-unicode.h>
19 #include "opendialog.H"
20 #include "libmail/mail.H"
21
22 #include <fstream>
23 #include <vector>
24 #include <iterator>
25 #include <algorithm>
26
27 #include <errno.h>
28 #include <string.h>
29
30 extern Gettext::Key key_ALL;
31 extern Gettext::Key key_CUT;
32 extern Gettext::Key key_JUSTIFY;
33 extern Gettext::Key key_EDITSEARCH;
34 extern Gettext::Key key_EDITREPLACE;
35 extern Gettext::Key key_GETFILE;
36 extern Gettext::Key key_DICTSPELL;
37 extern Gettext::Key key_EXTEDITOR;
38
39 extern Gettext::Key key_REPLACE0;
40 extern Gettext::Key key_REPLACE1;
41 extern Gettext::Key key_REPLACE2;
42 extern Gettext::Key key_REPLACE3;
43 extern Gettext::Key key_REPLACE4;
44 extern Gettext::Key key_REPLACE5;
45 extern Gettext::Key key_REPLACE6;
46 extern Gettext::Key key_REPLACE7;
47 extern Gettext::Key key_REPLACE8;
48 extern Gettext::Key key_REPLACE9;
49
50 extern Gettext::Key key_ABORT;
51 extern Gettext::Key key_MACRO;
52
53 extern Gettext::Key key_REPLACE_K;
54 extern Gettext::Key key_IGNORE_K;
55 extern Gettext::Key key_IGNOREALL_K;
56
57 extern char32_t ularr, urarr;
58 extern char32_t ucwrap;
59
60 extern CursesStatusBar *statusBar;
61 extern CursesMainScreen *mainScreen;
62
63 extern Demoronize *currentDemoronizer;
64
65 extern SpellCheckerBase *spellCheckerBase;
66
67 time_t CursesEditMessage::autosaveInterval=60;
68 std::string CursesEditMessage::externalEditor;
69
70 extern struct CustomColor color_md_headerName;
71
72 ////////////////////////////////////////////////////////////////////////////
73 //
74 // Helper class instantiated when spell checking begins.
75 // When this object goes out of scope, the spell checker object gets
76 // automatically cleaned up.
77 //
78
79 class CursesEditMessage::SpellCheckerManager {
80
81 public:
82 SpellCheckerBase::Manager *manager;
83
84 SpellCheckerManager();
85 ~SpellCheckerManager();
86
87 operator SpellCheckerBase::Manager *();
88
89 SpellCheckerBase::Manager *operator ->();
90 };
91
SpellCheckerManager()92 CursesEditMessage::SpellCheckerManager::SpellCheckerManager()
93 : manager(NULL)
94 {
95 }
96
~SpellCheckerManager()97 CursesEditMessage::SpellCheckerManager::~SpellCheckerManager()
98 {
99 if (manager)
100 delete manager;
101 }
102
103 CursesEditMessage::SpellCheckerManager::operator SpellCheckerBase::Manager *()
104 {
105 return manager;
106 }
107
108 SpellCheckerBase::Manager *CursesEditMessage::SpellCheckerManager::operator->()
109 {
110 return manager;
111 }
112
CursesEditMessage(CursesContainer * parent)113 CursesEditMessage::CursesEditMessage(CursesContainer *parent)
114 : Curses(parent), CursesKeyHandler(PRI_SCREENHANDLER),
115 cursorCol(0),
116 cursorLineHorizShift(0), marked(false),
117 lastKey((char32_t)0)
118 {
119 text_UTF8.push_back(CursesFlowedLine());
120 // Always have at least one line in text_UTF8
121 cursorRow.me=this;
122 }
123
~CursesEditMessage()124 CursesEditMessage::~CursesEditMessage()
125 {
126 }
127
cursorRowChanged()128 void CursesEditMessage::cursorRowChanged()
129 {
130 }
131
getWidth()132 int CursesEditMessage::getWidth() const
133 {
134 return getScreenWidth();
135 }
136
getHeight()137 int CursesEditMessage::getHeight() const
138 {
139 size_t n=numLines();
140
141 size_t h=getScreenHeight();
142
143 if (h > n)
144 n=h;
145
146 return n;
147 }
148
isFocusable()149 bool CursesEditMessage::isFocusable()
150 {
151 return true;
152 }
153
154 //
155 // Get a line of text as unicode characters
156 //
157
getText(size_t line,std::u32string & textRef)158 void CursesEditMessage::getText(size_t line,
159 std::u32string &textRef)
160 {
161 bool dummy;
162
163 return getText(line, textRef, dummy);
164 }
165
getText(size_t line,std::u32string & textRef,bool & flowedRet)166 void CursesEditMessage::getText(size_t line,
167 std::u32string &textRef,
168 bool &flowedRet)
169 {
170 textRef.clear();
171
172 if (line >= numLines())
173 return;
174
175 const CursesFlowedLine &l=text_UTF8[line];
176
177 unicode::iconvert::convert(l.text, "utf-8", textRef);
178 flowedRet=l.flowed;
179 }
180
getUTF8Text(size_t i,bool useflowedformat)181 std::string CursesEditMessage::getUTF8Text(size_t i,
182 bool useflowedformat)
183 {
184 CursesFlowedLine line;
185
186 getText(i, line);
187
188 if (useflowedformat)
189 {
190 if (line.flowed)
191 line.text += " ";
192 else
193 {
194 std::string::iterator b, e;
195
196 for (b=line.text.begin(), e=line.text.end();
197 b != e; --e)
198 {
199 if (e[-1] != ' ')
200 break;
201 }
202
203 line.text.erase(e, line.text.end());
204 }
205 }
206
207 return line.text;
208 }
209
210 //
211 // Save line of text as unicode chars.
212 //
213
setText(size_t line,const std::u32string & textRef)214 void CursesEditMessage::setText(size_t line,
215 const std::u32string &textRef)
216 {
217 if (line >= numLines())
218 abort();
219
220 text_UTF8[line]=CursesFlowedLine(textRef, false);
221 }
222
setText(size_t line,const CursesFlowedLine & textRef)223 void CursesEditMessage::setText(size_t line,
224 const CursesFlowedLine &textRef)
225 {
226 if (line >= numLines())
227 abort();
228
229 text_UTF8[line]=textRef;
230 }
231
getTextBeforeAfter(std::u32string & before,std::u32string & after)232 void CursesEditMessage::getTextBeforeAfter(std::u32string &before,
233 std::u32string &after)
234 {
235 bool dummy;
236
237 getTextBeforeAfter(before, after, dummy);
238 }
239
getTextBeforeAfter(std::u32string & before,std::u32string & after,bool & flowed)240 void CursesEditMessage::getTextBeforeAfter(std::u32string &before,
241 std::u32string &after,
242 bool &flowed)
243 {
244 // Get current line
245
246 {
247 CursesFlowedLine line;
248
249 getText(cursorRow, line);
250
251 flowed=line.flowed;
252 unicode::iconvert::convert(line.text, "utf-8", before);
253 }
254
255 // Find character cursor is at.
256
257 std::u32string::iterator pos
258 =getIndexOfCol(before, cursorCol);
259
260 // Split the line at that point.
261
262 after.clear();
263 after.insert(after.end(), pos, before.end());
264
265 before.erase(pos, before.end());
266 }
267
268 //
269 // Set the contents of the current line by concatenating two unicode characters
270 // and position the cursor between the two parts.
271 //
272 void CursesEditMessage
setTextBeforeAfter(const std::u32string & before,const std::u32string & after,bool flowed)273 ::setTextBeforeAfter(const std::u32string &before,
274 const std::u32string &after,
275 bool flowed)
276 {
277 widecharbuf origline;
278
279 origline.init_unicode(before.begin(), before.end());
280
281 std::u32string newline;
282
283 newline.reserve(before.size()+after.size());
284
285 newline.insert(newline.end(), before.begin(), before.end());
286 newline.insert(newline.end(), after.begin(), after.end());
287
288 setText(cursorRow, CursesFlowedLine(newline, flowed));
289
290 cursorCol=origline.wcwidth(0);
291 }
292
293 template<>
replaceTextLine(size_t line,const std::u32string & value)294 void CursesEditMessage::replaceTextLine(size_t line,
295 const std::u32string &value)
296 {
297 text_UTF8[line]=CursesFlowedLine(value, false);
298 }
299
300 template<>
replaceTextLine(size_t line,const CursesFlowedLine & value)301 void CursesEditMessage::replaceTextLine(size_t line,
302 const CursesFlowedLine &value)
303 {
304 text_UTF8[line]=value;
305 }
306
307 //
308 // Replace a range of existing text lines. New content is defined by
309 // a beginning and ending iterator, that must iterate over
310 // u32string.
311 //
312 // The iterators must be random access iterators.
313
314 template<typename iter_type>
replaceTextLinesImpl(size_t start,size_t cnt,iter_type beg_iter,iter_type end_iter)315 void CursesEditMessage::replaceTextLinesImpl(size_t start, size_t cnt,
316 iter_type beg_iter,
317 iter_type end_iter)
318 {
319 if (start > text_UTF8.size() || text_UTF8.size() - start < cnt)
320 return;
321
322 modified();
323
324 text_UTF8.erase(text_UTF8.begin() + start,
325 text_UTF8.begin() + start + cnt);
326
327 cnt=end_iter - beg_iter;
328
329 text_UTF8.reserve(text_UTF8.size()+cnt);
330
331 text_UTF8.insert(text_UTF8.begin() + start, cnt,
332 CursesFlowedLine());
333
334 while (cnt)
335 {
336 --cnt;
337 replaceTextLine(start, *beg_iter);
338 ++start;
339 ++beg_iter;
340 }
341
342 cursorRow=start;
343 cursorCol=0;
344 }
345
346 template<>
347 class CursesEditMessage::replace_text_lines_helper
348 <std::random_access_iterator_tag> {
349
350 public:
351
352 template<typename iter_type>
do_replace(CursesEditMessage * obj,size_t start,size_t cnt,iter_type beg_iter,iter_type end_iter)353 static void do_replace(CursesEditMessage *obj,
354 size_t start, size_t cnt,
355 iter_type beg_iter,
356 iter_type end_iter)
357 {
358 obj->replaceTextLinesImpl(start, cnt, beg_iter, end_iter);
359 }
360 };
361
362 template<typename non_random_access_iterator_tag>
363 class CursesEditMessage::replace_text_lines_helper {
364
365 public:
366
367 template<typename iter_type>
do_replace(CursesEditMessage * obj,size_t start,size_t cnt,iter_type beg_iter,iter_type end_iter)368 static void do_replace(CursesEditMessage *obj,
369 size_t start, size_t cnt,
370 iter_type beg_iter,
371 iter_type end_iter)
372 {
373 size_t n=256;
374
375 do
376 {
377 std::vector<typename std::iterator_traits<iter_type>
378 ::value_type> lines;
379
380 lines.reserve(n);
381
382 while (beg_iter != end_iter && lines.size() < n)
383 {
384 lines.push_back(*beg_iter);
385 ++beg_iter;
386 }
387
388 replace_text_lines_helper
389 <std::random_access_iterator_tag>
390 ::do_replace(obj, start, cnt,
391 lines.begin(), lines.end());
392 cnt=0;
393 start += lines.size();
394 } while (beg_iter != end_iter);
395 }
396 };
397
398 template<typename iter_type>
replaceTextLines(size_t start,size_t cnt,iter_type beg_iter,iter_type end_iter)399 void CursesEditMessage::replaceTextLines(size_t start, size_t cnt,
400 iter_type beg_iter,
401 iter_type end_iter)
402 {
403 typedef replace_text_lines_helper<typename std::iterator_traits<iter_type>
404 ::iterator_category> helper_t;
405
406 cursorRow=start+1;
407
408 helper_t::do_replace(this, start, cnt, beg_iter, end_iter);
409 if (text_UTF8.size() == 0)
410 text_UTF8.push_back(CursesFlowedLine());
411 // Always at least one line
412
413 /*
414 ** Required semantics: cursor is always on the last line of
415 ** the inserted text.
416 */
417 if (cursorRow >= text_UTF8.size())
418 cursorRow=text_UTF8.size();
419
420 if (cursorRow > 0)
421 --cursorRow;
422 }
423
justifiable(size_t lineNum)424 bool CursesEditMessage::justifiable(size_t lineNum)
425 {
426 if (lineNum < text_UTF8.size())
427 {
428 const CursesFlowedLine &l=text_UTF8[lineNum];
429
430 if (l.text.size() > 0)
431 switch (*l.text.begin()) {
432 case '>':
433 case ' ':
434 case '\t':
435 break;
436 default:
437 return true;
438 }
439 }
440 return false;
441 }
442
insertKeyPos(char32_t k)443 void CursesEditMessage::insertKeyPos(char32_t k)
444 {
445 if ( k >= 0 && k < ' ' && k != '\t')
446 return;
447
448 std::u32string before, after;
449 bool origWrapped;
450
451 getTextBeforeAfter(before, after, origWrapped);
452
453 before.insert(before.end(), k);
454
455 if (CursesMoronize::enabled)
456 {
457 char32_t buf[CursesMoronize::max_keycode_len+1];
458
459 size_t i;
460
461 std::u32string::iterator
462 b(before.begin()),
463 e(before.end());
464
465 for (i=0; i < sizeof(buf)/sizeof(buf[0])-1; ++i)
466 {
467 if (b == e)
468 break;
469 --e;
470
471 buf[i]=*e;
472 }
473
474 buf[i]=0;
475
476 if (i > 0)
477 {
478 std::u32string repl_c;
479
480 buf[i]=0;
481 i=CursesMoronize::moronize(buf, repl_c);
482
483 if (i > 0)
484 {
485 before.erase(before.end()-i, before.end());
486 before.insert(before.end(), repl_c.begin(),
487 repl_c.end());
488 }
489 }
490 }
491
492 std::string *macroptr=NULL;
493
494 Macros *mp=Macros::getRuntimeMacros();
495
496 if (mp)
497 {
498 std::map<Macros::name, std::string> ¯oMap=mp->macroList;
499
500 std::map<Macros::name, std::string>::iterator b=macroMap.begin(),
501 e=macroMap.end();
502
503 for ( ; b != e; ++b)
504 {
505 if (b->first.f)
506 continue;
507
508
509 if (b->first.n.size() > before.size() ||
510 !std::equal(b->first.n.begin(),
511 b->first.n.end(),
512 before.end() - b->first.n.size()))
513 continue;
514
515 before.erase(before.end() - b->first.n.size(),
516 before.end());
517
518 macroptr= &b->second;
519 break;
520 }
521 }
522
523 {
524 widecharbuf wc;
525
526 wc.init_unicode(before.begin(), before.end());
527 cursorCol=wc.wcwidth(0);
528 }
529
530 before.insert(before.end(), after.begin(),
531 after.end());
532 setText(cursorRow, CursesFlowedLine(before, origWrapped));
533
534 if (macroptr)
535 {
536 processMacroKey(*macroptr);
537 return;
538 }
539
540 inserted();
541 }
542
543 //
544 // A bare-bones iterator that retrieves lines from a file and converts
545 // them to unicode characters.
546 //
547
548 class CursesEditMessage::get_file_helper
549 : public std::iterator<std::input_iterator_tag, CursesFlowedLine> {
550
551 std::istream *i;
552
553 CursesFlowedLine line;
554
555 bool isflowed;
556 bool delsp;
557
binaryfile()558 void binaryfile()
559 {
560 statusBar->clearstatus();
561 statusBar->status(_("Binary file? You must be joking."),
562 statusBar->SYSERROR);
563 statusBar->beepError();
564 i=NULL;
565 }
566
nextline(bool first)567 void nextline(bool first)
568 {
569 std::string linetxt;
570
571 if (i == NULL)
572 return;
573
574 getline(*i, linetxt);
575
576 if (i->fail() && !i->eof())
577 {
578 i=NULL;
579 return;
580 }
581
582 if (i->eof() && linetxt.size() == 0)
583 {
584 i=NULL;
585 return;
586 }
587
588 if (first) // Check if it's a binary file
589 for (std::string::iterator
590 b(linetxt.begin()),
591 e(linetxt.end()); b != e;
592 ++b)
593 {
594 if (*b == 0)
595 {
596 binaryfile();
597 return;
598 }
599 }
600
601 bool flowedflag=false;
602
603 std::u32string uline;
604
605 unicode::iconvert::convert(linetxt,
606 unicode_default_chset(),
607 uline);
608
609 if (isflowed)
610 {
611 std::u32string::iterator b(uline.begin()),
612 e(uline.end());
613
614 while (b != e && *b == '>')
615 ++b;
616
617 if (b != e && *b == ' ')
618 ++b;
619
620 if (b != e && e[-1] == ' ')
621 {
622 flowedflag=true;
623 if (delsp)
624 uline.pop_back();
625 }
626 }
627
628 // Edit out control characters
629 std::u32string::iterator
630 b=uline.begin(), e=uline.end(), c=b;
631
632 for ( ; b != e; ++b)
633 {
634 if (*b < ' ' && *b != '\t')
635 continue;
636
637 *c++=*b;
638 }
639
640 uline.erase(c, e);
641
642 line=CursesFlowedLine(uline, flowedflag);
643 }
644
645 public:
get_file_helper(std::istream & iArg,bool isflowedArg,bool delspArg)646 get_file_helper(std::istream &iArg,
647 bool isflowedArg,
648 bool delspArg) : i(&iArg),
649 isflowed(isflowedArg),
650 delsp(delspArg)
651 {
652 nextline(true);
653 }
654
get_file_helper()655 get_file_helper() : i(NULL), isflowed(false), delsp(false)
656 {
657 }
658
659 const CursesFlowedLine &operator*()
660 {
661 return line;
662 }
663
664 get_file_helper &operator++()
665 {
666 nextline(false);
667 return *this;
668 }
669
670 bool operator==(const get_file_helper &o)
671 {
672 return i == NULL && o.i == NULL;
673 }
674
675 bool operator!=(const get_file_helper &o)
676 {
677 return !operator==(o);
678 }
679 };
680
681 //
682 // Justification rewrap helper. unicode_wordwrap_iterator saves the starting
683 // offset of each line into starting_offsets, as it iterates over the
684 // unjustified text.
685
686 class CursesEditMessage::unicode_wordwrap_rewrapper {
687
688 public:
689 mutable std::list<size_t> starting_offsets;
690
operator()691 bool operator()(size_t n) const
692 {
693 while (!starting_offsets.empty() &&
694 starting_offsets.front() < n)
695 starting_offsets.pop_front();
696
697 return !starting_offsets.empty() &&
698 starting_offsets.front() == n;
699 }
700 };
701
702 //
703 // Iterator over unjustified text, for unicodewordwrap()
704 //
705
706 class CursesEditMessage::unicode_wordwrap_iterator
707 : public std::iterator<std::input_iterator_tag,
708 char32_t> {
709
710 CursesEditMessage *msg;
711 size_t start_row, end_row;
712 unicode_wordwrap_rewrapper *rewrapper;
713
714 size_t cnt;
715
716 public:
unicode_wordwrap_iterator()717 unicode_wordwrap_iterator() // Ending iterator
718 : msg(NULL), start_row(0), end_row(0),
719 rewrapper(NULL), cnt(0)
720 {
721 }
722
unicode_wordwrap_iterator(CursesEditMessage * msgArg,size_t start_rowArg,size_t end_rowArg,unicode_wordwrap_rewrapper & rewrapperArg)723 unicode_wordwrap_iterator(// The message object
724 CursesEditMessage *msgArg,
725
726 // First row to iterate over
727 size_t start_rowArg,
728
729 // Last row+1 to iterate over
730 size_t end_rowArg,
731
732 // Save starting offsets of each row
733 // in here.
734 unicode_wordwrap_rewrapper &rewrapperArg)
735 : msg(msgArg), start_row(start_rowArg),
736 end_row(end_rowArg),
737 rewrapper(&rewrapperArg),
738 cnt(0)
739 {
740 newline();
741 }
742
743 // Test for begin=end equality
744
745 bool operator==(const unicode_wordwrap_iterator &o) const
746 {
747 return start_row == end_row && o.start_row == o.end_row;
748 }
749
750 bool operator!=(const unicode_wordwrap_iterator &o) const
751 {
752 return !operator==(o);
753 }
754
755 private:
756
757 // Current unjustified line
758 std::u32string line;
759
760 // Current iterator over the line
761 std::u32string::iterator b, e;
762
763 // Advanced to the next line in the unjustified text. Set up the
764 // beginning and the ending iterator
newline()765 void newline()
766 {
767 while (start_row < end_row)
768 {
769 msg->getText(start_row, line);
770
771 b=line.begin();
772 e=line.end();
773
774 // Trim any trailing space
775
776 while (b != e)
777 {
778 if (e[-1] != ' ')
779 break;
780 --e;
781 }
782
783 if (b != e)
784 {
785 rewrapper->starting_offsets.push_back(cnt);
786 break;
787 }
788
789 // Ignore empty lines
790 ++start_row;
791 }
792 }
793
794 public:
795
796 // Iterator operation
797 char32_t operator*() const
798 {
799 return *b;
800 }
801
802 // Increment iterator
803 unicode_wordwrap_iterator &operator++()
804 {
805 if (start_row < end_row)
806 {
807 ++cnt;
808
809 if (++b == e)
810 {
811 ++start_row;
812 newline();
813 }
814 }
815 return *this;
816 }
817 };
818
819 // Collect wrapped content into a flowed line list
820
821 class CursesEditMessage::unicode_wordwrap_oiterator
822 : public std::iterator<std::output_iterator_tag, void, void, void, void>
823 {
824
825 unicode_wordwrap_oiterator(const unicode_wordwrap_oiterator &);
826 unicode_wordwrap_oiterator &operator=(const unicode_wordwrap_oiterator
827 &);
828
829 public:
830 std::list<CursesFlowedLine> wrapped;
831
unicode_wordwrap_oiterator()832 unicode_wordwrap_oiterator() {}
833
834 unicode_wordwrap_oiterator &operator++() { return *this; }
835 unicode_wordwrap_oiterator &operator++(int) { return *this; }
836 unicode_wordwrap_oiterator &operator*() { return *this; }
837
838 unicode_wordwrap_oiterator &operator=(const std::u32string
839 &u)
840 {
841 wrapped.push_back(CursesFlowedLine(u, true));
842 return *this;
843 }
844
845 };
846
processKeyInFocus(const Key & key)847 bool CursesEditMessage::processKeyInFocus(const Key &key)
848 {
849 Key lastKeyProcessed=lastKey;
850
851 lastKey=key;
852
853 if (cursorRow >= numLines())
854 abort();
855
856 // TODO: Check for plain key
857
858 if (key == key.SHIFTLEFT || key == key.SHIFTRIGHT ||
859 key == key.SHIFTUP || key == key.SHIFTDOWN ||
860 key == key.SHIFTHOME || key == key.SHIFTEND ||
861 key == key.SHIFTPGUP || key == key.SHIFTPGDN)
862 {
863 if (!marked)
864 mark();
865 }
866 else if (marked && key != key.DEL && key != key.BACKSPACE &&
867 key != key_CUT && key != key.CLREOL && key != key_MACRO)
868 {
869 marked=false;
870 draw();
871 }
872
873 if (key == key.UP || key == key.SHIFTUP)
874 {
875 if (cursorRow == 0)
876 transferPrevFocus();
877 else
878 {
879 --cursorRow;
880 drawLine(cursorRow+1);
881 }
882 drawLine(cursorRow);
883 return true;
884 }
885
886 if (key == key.DOWN || key == key.SHIFTDOWN)
887 {
888 if (cursorRow + 1 >= numLines())
889 transferNextFocus();
890 else
891 {
892 ++cursorRow;
893 drawLine(cursorRow-1);
894 }
895 drawLine(cursorRow);
896 return true;
897 }
898
899 if (key == key.LEFT || key == key.SHIFTLEFT)
900 {
901 left(true);
902 return true;
903 }
904
905 if (key == key.RIGHT || key == key.SHIFTRIGHT)
906 {
907 right();
908 return true;
909 }
910
911 if (key == key.HOME || key == key.SHIFTHOME)
912 {
913 cursorCol=0;
914 cursorLineHorizShift=0;
915 drawLine(cursorRow);
916 return true;
917 }
918
919 if (key == key.END || key == key.SHIFTEND)
920 {
921 end();
922 return true;
923 }
924
925 if (key == key.PGUP || key == key.SHIFTPGUP)
926 {
927 return Curses::processKeyInFocus(key);
928 #if 0
929 size_t dummy;
930 size_t h;
931
932 getVerticalViewport(dummy, h);
933
934 drawLine(cursorRow);
935 cursorRow -= cursorRow >= h ? h:cursorRow;
936 drawLine(cursorRow);
937 return true;
938 #endif
939 }
940
941 if (key == key.PGDN || key == key.SHIFTPGDN)
942 {
943 return Curses::processKeyInFocus(key);
944 }
945
946 if (key == key.ENTER)
947 {
948 enterKey();
949 draw();
950 return true;
951 }
952
953 if (key == key.BACKSPACE)
954 {
955 left(false);
956 deleteChar(false);
957 return true;
958 }
959
960 if (key == key.DEL || (marked && key == key_CUT))
961 {
962 deleteChar(false);
963 return true;
964 }
965
966 if (key == key_JUSTIFY)
967 {
968 if (lastKeyProcessed == key_JUSTIFY) // Undo justification
969 {
970 if (CursesField::cutBuffer.empty())
971 return true;
972
973 // Find where the justified paragraph begins.
974
975 size_t r=cursorRow+1;
976
977 while (r > 0)
978 {
979 --r;
980
981 if (!justifiable(r))
982 {
983 ++r;
984 break;
985 }
986 }
987
988 erase(); // Clear the screen.
989
990 replaceTextLines(r, cursorRow-r+1,
991 CursesField::cutBuffer.begin(),
992 CursesField::cutBuffer.end());
993 CursesField::cutBuffer.clear();
994 lastKey=Key((char32_t)0); // Not really
995 draw();
996 return true;
997 }
998
999 // We can only justify if the cursor is on a non-empty
1000 // line that does not begin with quoted text.
1001
1002 if (!justifiable(cursorRow))
1003 {
1004 lastKey=Key((char32_t)0); // Not really
1005 return true;
1006 }
1007
1008 // Find start of paragraph
1009
1010 while (cursorRow > 0)
1011 {
1012 if (!justifiable(--cursorRow))
1013 {
1014 ++cursorRow;
1015 break;
1016 }
1017 }
1018
1019 size_t startingRow=cursorRow;
1020
1021 CursesField::cutBuffer.clear();
1022
1023 // Original, unjustified text goes here.
1024
1025 while (cursorRow < numLines())
1026 {
1027 if (!justifiable(cursorRow))
1028 break;
1029
1030 CursesFlowedLine line;
1031 getText(cursorRow, line);
1032 CursesField::cutBuffer.push_back(line);
1033 ++cursorRow;
1034 }
1035 erase();
1036
1037 unicode_wordwrap_oiterator insert_iter;
1038
1039 unicode_wordwrap_rewrapper rewraphelper;
1040
1041 unicodewordwrap(unicode_wordwrap_iterator(this,
1042 startingRow,
1043 cursorRow+1,
1044 rewraphelper),
1045 unicode_wordwrap_iterator(this,
1046 startingRow,
1047 startingRow,
1048 rewraphelper),
1049 rewraphelper,
1050 insert_iter,
1051 LINEW, false);
1052
1053 if (insert_iter.wrapped.empty())
1054 insert_iter.wrapped.push_back(CursesFlowedLine());
1055
1056 insert_iter.wrapped.back().flowed=false;
1057 // Last line does not flow
1058
1059 replaceTextLines(startingRow, cursorRow-startingRow,
1060 insert_iter.wrapped.begin(),
1061 insert_iter.wrapped.end());
1062
1063 marked=false;
1064 draw();
1065 statusBar->status(_("^J: undo justification"));
1066 return true;
1067 }
1068
1069 if (key == key.CLREOL)
1070 {
1071 if (lastKeyProcessed != key.CLREOL)
1072 CursesField::cutBuffer.clear();
1073
1074 mark();
1075 end();
1076 deleteChar(true);
1077 return true;
1078 }
1079
1080 if (key == key.SHIFT)
1081 {
1082 statusBar->clearstatus();
1083 if (shiftmode)
1084 statusBar->status(_("Mark set"));
1085
1086 return true;
1087 }
1088
1089 if (key == key_EDITSEARCH || key == key_EDITREPLACE)
1090 {
1091 MONITOR(CursesEditMessage);
1092
1093 myServer::promptInfo response=
1094 myServer
1095 ::prompt(myServer
1096 ::promptInfo(Gettext(_("Search: ")))
1097 .initialValue(defaultSearchStr));
1098
1099 if (DESTROYED() || response.abortflag ||
1100 response.value.size() == 0)
1101 return true;
1102
1103 defaultSearchStr=response.value;
1104
1105 // We match in UTF-8, convert the search string to UTF8,
1106 // and initialize the search engine.
1107
1108 mail::Search searchEngine;
1109
1110 bool doSmartReplace=true;
1111
1112 // If the search string is completely in lowercase, do a
1113 // "smart" replace (replace wholly uppercase strings with
1114 // uppercase replacement text, and replace title-cased string
1115 // with title-cased replacement text).
1116
1117 {
1118 std::u32string ubuf;
1119
1120 unicode::iconvert::convert(defaultSearchStr,
1121 unicode_default_chset(),
1122 ubuf);
1123
1124 for (std::u32string::iterator
1125 b(ubuf.begin()), e(ubuf.end()); b != e;
1126 ++b)
1127 {
1128 if (*b != unicode_lc(*b))
1129 {
1130 doSmartReplace=false;
1131 break;
1132 }
1133 }
1134 }
1135
1136 if (!searchEngine.setString(defaultSearchStr.c_str(),
1137 unicode_default_chset()))
1138 return false;
1139
1140 if (searchEngine.getSearchLen() == 0)
1141 return true;
1142
1143 if (key == key_EDITSEARCH) // A single search
1144 {
1145 search(true, true, searchEngine);
1146 return true;
1147 }
1148
1149 // Search/replace
1150
1151 response=myServer
1152 ::prompt(myServer
1153 ::promptInfo(Gettext(_("Replace: ")))
1154 .initialValue(defaultReplaceStr));
1155
1156 if (DESTROYED() || response.abortflag)
1157 return true;
1158
1159 defaultReplaceStr=response.value;
1160
1161 std::string upperReplaceStr=defaultReplaceStr;
1162 std::string titleReplaceStr=defaultReplaceStr;
1163
1164 {
1165 std::u32string ubuf;
1166
1167 if (unicode::iconvert::convert(defaultReplaceStr,
1168 unicode_default_chset(),
1169 ubuf))
1170 {
1171 std::u32string uc=ubuf;
1172
1173 for (std::u32string::iterator
1174 b(uc.begin()),
1175 e(uc.end());
1176 b != e; ++b)
1177 *b=unicode_uc(*b);
1178
1179 bool err;
1180
1181 std::string r(unicode::iconvert::convert
1182 (uc, unicode_default_chset(),
1183 err));
1184
1185 if (!err)
1186 upperReplaceStr=r;
1187
1188 uc=ubuf;
1189 if (uc.size() != 0)
1190 uc[0]=unicode_tc(uc[0]);
1191
1192 r=unicode::iconvert::convert
1193 (uc, unicode_default_chset(), err);
1194
1195 if (!err)
1196 titleReplaceStr=r;
1197 }
1198 }
1199
1200 size_t replaceCnt=0;
1201 bool globalReplace=false;
1202
1203 while (search(!globalReplace, false, searchEngine))
1204 {
1205 if (!globalReplace)
1206 {
1207 response=
1208 myServer
1209 ::promptInfo(Gettext(_("Replace"
1210 "? (Y/N) ")))
1211 .yesno()
1212 .option( key_ALL,
1213 Gettext::keyname(_("ALL_K:A")
1214 ),
1215 _("Replace All"));
1216
1217 response=myServer::prompt(response);
1218
1219 if (DESTROYED() || response.abortflag)
1220 return true;
1221
1222 if (key_ALL == response.firstChar())
1223 globalReplace=true;
1224 else if (response.value != "Y")
1225 continue;
1226 }
1227
1228 CursesField::cutBuffer.clear();
1229 deleteChar(false); // Found search string is marked.
1230
1231 std::string replaceStr=defaultReplaceStr;
1232
1233 if (doSmartReplace && !CursesField::cutBuffer.empty())
1234 {
1235 std::u32string ubuf;
1236
1237 if (unicode::iconvert::
1238 convert(CursesField::cutBuffer.front().text,
1239 "utf-8", ubuf))
1240 {
1241 std::u32string::iterator
1242 b, e;
1243
1244 for (b=ubuf.begin(),
1245 e=ubuf.end(); b != e; ++b)
1246 if (unicode_uc(*b) != *b)
1247 break;
1248
1249 if (b == e)
1250 replaceStr=upperReplaceStr;
1251 else if (ubuf.size() > 0 &&
1252 ubuf[0] == unicode_tc(ubuf[0]))
1253 replaceStr=titleReplaceStr;
1254 }
1255 }
1256
1257 CursesField::cutBuffer.clear();
1258 CursesField::cutBuffer.push_back(replaceStr);
1259
1260 yank(CursesField::cutBuffer,
1261 unicode_default_chset(),
1262 false);
1263
1264 ++replaceCnt;
1265 }
1266 draw();
1267 statusBar->clearstatus();
1268 statusBar->status(Gettext(_("Replaced %1% occurences."))
1269 << replaceCnt);
1270 return true;
1271 }
1272
1273 if (key == key_GETFILE)
1274 {
1275 std::string filename;
1276
1277 {
1278 OpenDialog open_dialog;
1279
1280 open_dialog.noMultiples();
1281
1282 open_dialog.requestFocus();
1283 myServer::eventloop();
1284
1285 std::vector<std::string> &filenameList=
1286 open_dialog.getFilenameList();
1287
1288 if (filenameList.size() == 0)
1289 filename="";
1290 else
1291 filename=filenameList[0];
1292
1293 mainScreen->erase();
1294 }
1295 mainScreen->draw();
1296 requestFocus();
1297
1298 if (filename.size() == 0)
1299 return true;
1300
1301 std::ifstream i(filename.c_str());
1302
1303 if (!i.is_open())
1304 {
1305 statusBar->clearstatus();
1306 statusBar->status(strerror(errno),
1307 statusBar->SYSERROR);
1308 statusBar->beepError();
1309 return true;
1310 }
1311
1312 get_file_helper beg_iter(i, false, false), end_iter;
1313
1314 replaceTextLines(cursorRow < numLines() ?
1315 cursorRow:numLines(), 0,
1316 beg_iter, end_iter);
1317 draw();
1318 return true;
1319 }
1320
1321 if (key == key_DICTSPELL)
1322 {
1323 std::u32string currentLine;
1324 size_t row=cursorRow;
1325
1326 getText(row, currentLine);
1327
1328 size_t pos=getIndexOfCol(currentLine, cursorCol)
1329 - currentLine.begin();
1330
1331 std::string errmsg;
1332
1333 SpellCheckerBase::Manager *managerPtr=
1334 spellCheckerBase->getManager(errmsg);
1335
1336 if (!managerPtr)
1337 {
1338 statusBar->status(errmsg);
1339 statusBar->beepError();
1340 return true;
1341 }
1342
1343 SpellCheckerManager manager;
1344
1345 manager.manager=managerPtr;
1346
1347 MONITOR(CursesEditMessage);
1348
1349 bool needPrompt=true;
1350
1351 while (!DESTROYED())
1352 {
1353 if (needPrompt)
1354 {
1355 statusBar->clearstatus();
1356 statusBar->status(_("Spell checking..."));
1357 statusBar->flush();
1358 needPrompt=false;
1359 }
1360
1361 if ((currentLine.size() > 0 && currentLine[0] == '>')
1362 // Do not spellcheck quoted content
1363 ||
1364 pos >= currentLine.size())
1365 {
1366 if (++row >= numLines())
1367 break;
1368
1369 currentLine.clear();
1370 getText(row, currentLine);
1371 pos=0;
1372 continue;
1373 }
1374
1375 // Grab the next word
1376
1377 size_t i;
1378
1379 {
1380 unicode::wordbreakscan scanner;
1381
1382 for (size_t j=pos; j<currentLine.size(); ++j)
1383 if (scanner << currentLine[j])
1384 break;
1385
1386 i=pos + scanner.finish();
1387 }
1388
1389 if (i == pos)
1390 {
1391 ++pos;
1392 continue;
1393 }
1394
1395 // Convert word to utf-8
1396
1397 std::string word_c;
1398
1399 {
1400 std::u32string word;
1401
1402 word.insert(word.end(),
1403 currentLine.begin()+pos,
1404 currentLine.begin()+i);
1405 word.push_back(0);
1406
1407 word_c=unicode::iconvert::convert(word, "utf-8");
1408 }
1409
1410 bool found_flag;
1411 std::string errmsg;
1412
1413 // Spell check.
1414
1415 if (word_c.size() == 0 ||
1416 (errmsg=manager->search(word_c, found_flag))
1417 .size() > 0)
1418 {
1419 statusBar->clearstatus();
1420 statusBar->status(errmsg);
1421 statusBar->beepError();
1422 break;
1423 }
1424
1425 if (found_flag)
1426 {
1427 pos=i;
1428 continue;
1429 }
1430
1431 // Highlight misspelled word
1432
1433 cursorRow=row;
1434 cursorCol=getTextHorizPos(currentLine, i);
1435 mark();
1436
1437 int dummy1, dummy2;
1438 getCursorPosition(dummy1, dummy2);
1439 // Make sure starting position is shown.
1440
1441 cursorCol=getTextHorizPos(currentLine, pos);
1442 drawLine(cursorRow);
1443
1444 bool doReplace;
1445 std::string origWord=word_c;
1446
1447 needPrompt=true;
1448
1449 if (!checkReplace(doReplace, word_c, manager.manager))
1450 {
1451 marked=false;
1452 drawLine(row);
1453 statusBar->draw();
1454 statusBar->clearstatus();
1455 return true;
1456 }
1457
1458 std::u32string uc;
1459
1460
1461 if (doReplace &&
1462 unicode::iconvert::convert(word_c, "utf-8",
1463 uc))
1464 {
1465 modified();
1466
1467 currentLine.erase(currentLine.begin() + pos,
1468 currentLine.begin()+i);
1469
1470 currentLine.insert(currentLine.begin() + pos,
1471 uc.begin(), uc.end());
1472
1473 setText(row, currentLine);
1474
1475 pos += uc.size();
1476
1477 cursorCol=getTextHorizPos(currentLine, pos);
1478 manager->replace(origWord, word_c);
1479 }
1480 else
1481 {
1482 pos=i;
1483 }
1484 marked=false;
1485 drawLine(row);
1486 }
1487
1488 statusBar->status(_("Spell checking completed."));
1489 statusBar->draw();
1490 return true;
1491 }
1492
1493 if (key == key_EXTEDITOR)
1494 {
1495 if (externalEditor.size() == 0)
1496 return true; // leaf
1497
1498 statusBar->clearstatus();
1499 statusBar->status(_("Starting editor..."));
1500 statusBar->flush();
1501
1502 std::string msgfile=getConfigDir() + "/message.tmp";
1503
1504 std::ofstream o(msgfile.c_str());
1505
1506 if (!o.is_open())
1507 {
1508 statusBar->clearstatus();
1509 statusBar->status(strerror(errno));
1510 statusBar->beepError();
1511 return true;
1512 }
1513
1514 save(o, true);
1515 o.flush();
1516 if (o.fail() || o.bad())
1517 {
1518 statusBar->clearstatus();
1519 statusBar->status(strerror(errno));
1520 statusBar->beepError();
1521 return true;
1522 }
1523 o.close();
1524
1525 std::vector<const char *> args;
1526
1527 {
1528 const char *p=getenv("SHELL");
1529
1530 if (!p || !*p)
1531 p="/bin/sh";
1532
1533 args.push_back(p);
1534 }
1535
1536 args.push_back("-c");
1537
1538 erase();
1539 flush();
1540 int rc;
1541
1542 {
1543 std::string cmd=externalEditor + " " + msgfile;
1544
1545 args.push_back(cmd.c_str());
1546 args.push_back(0);
1547
1548 rc=Curses::runCommand(args, -1, "");
1549 }
1550 draw();
1551 cursorRow=0;
1552 cursorCol=0;
1553 {
1554 int dummy1, dummy2;
1555
1556 getCursorPosition(dummy1, dummy2);
1557 }
1558
1559 extedited();
1560 if (rc)
1561 {
1562 unlink(msgfile.c_str());
1563
1564 statusBar->clearstatus();
1565 statusBar->
1566 status(Gettext(_("The external editor command "
1567 "failed. Check that the "
1568 "configured external editor, "
1569 "\"%1%\", exists."))
1570 << externalEditor);
1571 statusBar->beepError();
1572 return true;
1573 }
1574
1575 std::ifstream i(msgfile.c_str());
1576
1577 if (!i.is_open())
1578 {
1579 unlink(msgfile.c_str());
1580 statusBar->clearstatus();
1581 statusBar->status(strerror(errno));
1582 statusBar->beepError();
1583 return true;
1584 }
1585
1586 get_file_helper beg_iter(i, true, true), end_iter;
1587
1588 replaceTextLines(0, numLines(), beg_iter, end_iter);
1589 cursorRow=0;
1590 i.close();
1591 unlink(msgfile.c_str());
1592 draw();
1593 return true;
1594 }
1595
1596 if (key == key_MACRO)
1597 {
1598 Macros *m=Macros::getRuntimeMacros();
1599
1600 if (!m)
1601 return true; // Leaf
1602
1603 myServer::promptInfo macroNamePrompt=
1604 myServer::promptInfo(marked ?
1605 _("Define macro shortcut: ")
1606 :
1607 _("Undefine macro shortcut: "));
1608
1609 macroNamePrompt.optionHelp
1610 .push_back(std::make_pair(Gettext
1611 ::keyname(_("ENTER:Enter")),
1612 marked ?
1613 _("Define shortcut")
1614 :
1615 _("Clear shortcut")
1616 ));
1617 macroNamePrompt.optionHelp
1618 .push_back(std::make_pair(Gettext::keyname(_("FKEY:Fn")),
1619 marked ?
1620 _("Assign shortcut to"
1621 " function key")
1622 :
1623 _("Undefine function key shortcut")
1624 ));
1625
1626 Macros::name macroName(0);
1627
1628 {
1629 FKeyTrapHandler macroHelper;
1630
1631 macroNamePrompt=myServer::prompt(macroNamePrompt);
1632
1633 if (macroHelper.defineFkeyFlag)
1634 {
1635 macroName=Macros::name(macroHelper.fkeyNum);
1636 }
1637 else
1638 {
1639 if (macroNamePrompt.abortflag)
1640 return true;
1641
1642 std::u32string v;
1643
1644 unicode::iconvert::convert(((std::string)
1645 macroNamePrompt),
1646 unicode_default_chset(),
1647 v);
1648
1649 std::u32string::iterator b, e;
1650
1651 for (b=v.begin(), e=v.end(); b != e; ++b)
1652 {
1653 if ((unsigned char)*b == *b &&
1654 strchr(" \t\r\n",
1655 (unsigned char)*b))
1656 {
1657 statusBar->clearstatus();
1658 statusBar->status(_("Macro name may not contain spaces."));
1659 statusBar->beepError();
1660 return true;
1661 }
1662 }
1663
1664 if (v.size() == 0)
1665 return true;
1666
1667 macroName=Macros::name(v);
1668 }
1669 }
1670
1671 if (myServer::nextScreen)
1672 return true;
1673
1674 if (!marked)
1675 {
1676 std::map<Macros::name, std::string>::iterator p=
1677 m->macroList.find(macroName);
1678
1679 if (p != m->macroList.end())
1680 m->macroList.erase(p);
1681
1682 macroDefined();
1683 statusBar->clearstatus();
1684 statusBar->status(_("Macro undefined."));
1685 return true;
1686 }
1687
1688 size_t row1, pos1, row2, pos2;
1689
1690 getMarkedRegion(row1, row2, pos1, pos2);
1691
1692 if (row1 == row2 && pos1 == pos2)
1693 return true; // Shouldn't happen
1694
1695
1696 std::string cutText;
1697
1698 size_t i;
1699
1700 for (i=row1; i <= row2; i++)
1701 {
1702 std::u32string u;
1703
1704 getText(i, u);
1705
1706 if (i == row2)
1707 u.erase(u.begin() + pos2, u.end());
1708
1709 if (i == row1)
1710 u.erase(u.begin(), u.begin() + pos1);
1711
1712 if (i < row2)
1713 u.push_back('\n');
1714
1715 cutText += unicode::iconvert::convert(u, "utf-8");
1716 }
1717
1718 std::map<Macros::name, std::string>::iterator p=
1719 m->macroList.find(macroName);
1720
1721 if (p != m->macroList.end())
1722 m->macroList.erase(p);
1723
1724 m->macroList.insert(std::make_pair(macroName, cutText));
1725
1726 macroDefined();
1727 statusBar->clearstatus();
1728 statusBar->status(_("Macro defined."));
1729 return true;
1730 }
1731
1732 if (key.fkey())
1733 {
1734 Macros::name fk(key.fkeynum());
1735
1736 Macros *mp=Macros::getRuntimeMacros();
1737
1738 if (!mp)
1739 return true;
1740
1741 std::map<Macros::name, std::string> &m=mp->macroList;
1742
1743 std::map<Macros::name, std::string>::iterator p=
1744 m.find(fk);
1745
1746 if (p != m.end())
1747 processMacroKey(p->second);
1748 return true;
1749 }
1750
1751 if (key.plain())
1752 {
1753 if (key.ukey == '\x03')
1754 return false;
1755
1756 if (key.ukey == CursesField::yankKey)
1757 {
1758 yank(CursesField::cutBuffer,
1759 unicode_default_chset(),
1760 true);
1761
1762 return true;
1763 }
1764
1765 insertKeyPos(key.ukey);
1766 return true;
1767 }
1768
1769 return false;
1770 }
1771
macroDefined()1772 void CursesEditMessage::macroDefined()
1773 {
1774 }
1775
processMacroKey(std::string & repl_utf8)1776 void CursesEditMessage::processMacroKey(std::string &repl_utf8)
1777 {
1778 std::list<CursesFlowedLine> flowedlist;
1779
1780 std::string::const_iterator b(repl_utf8.begin()), e(repl_utf8.end());
1781
1782 while (b != e)
1783 {
1784 std::string::const_iterator p=std::find(b, e, '\n');
1785
1786 flowedlist.push_back(std::string(b, p));
1787
1788 if ((b=p) != e)
1789 ++b;
1790 }
1791
1792 yank(flowedlist, "utf-8", true);
1793 }
1794
getConfigDir()1795 std::string CursesEditMessage::getConfigDir()
1796 {
1797 return "";
1798 }
1799
extedited()1800 void CursesEditMessage::extedited()
1801 {
1802 }
1803
1804 //////////////////////////////////////////////////////////////////////////
1805 //
1806 // Spell check replace prompt: suggestions, or manual choices.
1807
1808 class CursesEditMessage::ReplacePrompt : public CursesKeyHandler {
1809
1810 std::vector<std::string> &wordlist;
1811
1812 bool processKey(const Curses::Key &key);
1813 bool listKeys( std::vector< std::pair<std::string, std::string> > &list);
1814
1815 public:
1816
1817 enum ReplaceAction {
1818 replaceCustom,
1819 replaceWord,
1820 replaceIgnore,
1821 replaceIgnoreAll,
1822 replaceAbort} replaceAction;
1823
1824 std::string replaceWord_UTF8;
1825
1826 ReplacePrompt( std::vector<std::string> &wordlistArg);
1827 ~ReplacePrompt();
1828
1829 ReplaceAction prompt();
1830 };
1831
checkReplace(bool & doReplace,std::string & replaceWord,SpellCheckerBase::Manager * manager)1832 bool CursesEditMessage::checkReplace(bool &doReplace, std::string &replaceWord,
1833 SpellCheckerBase::Manager *manager)
1834 {
1835 std::vector<std::string> suggestions;
1836 std::string errmsg;
1837
1838 if (!manager->suggestions(replaceWord, suggestions, errmsg))
1839 {
1840 statusBar->clearstatus();
1841 statusBar->status(errmsg);
1842 statusBar->beepError();
1843 return false;
1844 }
1845
1846 MONITOR(CursesEditMessage);
1847
1848 for (;;)
1849 {
1850 ReplacePrompt::ReplaceAction action;
1851 std::string actionWord;
1852
1853 {
1854 statusBar->status(_("Replace misspelled word with:"));
1855 ReplacePrompt prompt(suggestions);
1856
1857 action=prompt.prompt();
1858 actionWord=prompt.replaceWord_UTF8;
1859 }
1860
1861 if (myServer::nextScreen || DESTROYED())
1862 return false; // Something happened...
1863
1864 Curses::keepgoing=true;
1865
1866 switch (action) {
1867 case ReplacePrompt::replaceWord:
1868 replaceWord=actionWord;
1869 doReplace=true;
1870 return true;
1871 case ReplacePrompt::replaceIgnore:
1872 doReplace=false;
1873 return true;
1874 case ReplacePrompt::replaceIgnoreAll:
1875 if ((errmsg=manager->addToSession(replaceWord)).size()
1876 > 0)
1877 {
1878 statusBar->clearstatus();
1879 statusBar->status(errmsg);
1880 statusBar->beepError();
1881 return false;
1882 }
1883 doReplace=false;
1884 return true;
1885 case ReplacePrompt::replaceAbort:
1886 return false;
1887 default:
1888 break;
1889 }
1890
1891 myServer::promptInfo response=
1892 myServer
1893 ::prompt(myServer
1894 ::promptInfo(Gettext(_("Replace with: "))));
1895
1896 if (response.abortflag ||
1897 response.value.size() == 0)
1898 continue;
1899
1900 replaceWord=unicode::iconvert::convert(std::string(response),
1901 unicode_default_chset(),
1902 "utf-8");
1903 break;
1904 }
1905
1906 doReplace=true;
1907 return true;
1908 }
1909
ReplacePrompt(std::vector<std::string> & wordlistArg)1910 CursesEditMessage::ReplacePrompt::ReplacePrompt( std::vector<std::string> &wordlistArg)
1911 : CursesKeyHandler(PRI_STATUSHANDLER), wordlist(wordlistArg),
1912 replaceAction(replaceCustom)
1913 {
1914 }
1915
~ReplacePrompt()1916 CursesEditMessage::ReplacePrompt::~ReplacePrompt()
1917 {
1918 }
1919
listKeys(std::vector<std::pair<std::string,std::string>> & list)1920 bool CursesEditMessage::ReplacePrompt::listKeys( std::vector< std::pair<std::string, std::string> >
1921 &list)
1922 // List of suggestions.
1923 {
1924 std::string keyname[10];
1925
1926 keyname[0]=Gettext::keyname(_("REPLACE0:0"));
1927 keyname[1]=Gettext::keyname(_("REPLACE1:1"));
1928 keyname[2]=Gettext::keyname(_("REPLACE2:2"));
1929 keyname[3]=Gettext::keyname(_("REPLACE3:3"));
1930 keyname[4]=Gettext::keyname(_("REPLACE4:4"));
1931 keyname[5]=Gettext::keyname(_("REPLACE5:5"));
1932 keyname[6]=Gettext::keyname(_("REPLACE6:6"));
1933 keyname[7]=Gettext::keyname(_("REPLACE7:7"));
1934 keyname[8]=Gettext::keyname(_("REPLACE8:8"));
1935 keyname[9]=Gettext::keyname(_("REPLACE9:9"));
1936
1937 size_t i;
1938
1939 for (i=0; i<10 && i<wordlist.size(); i++)
1940 {
1941 bool errflag;
1942
1943 std::string s=unicode::iconvert::convert(wordlist[i], "utf-8",
1944 unicode_default_chset(),
1945 errflag);
1946
1947 if (errflag)
1948 continue;
1949
1950 list.push_back(std::make_pair(keyname[i], s));
1951 }
1952 list.push_back(std::make_pair(Gettext::keyname(_("REPLACE:R")),
1953 _("Replace")));
1954 list.push_back(std::make_pair(Gettext::keyname(_("IGNORE:I")),
1955 _("Ignore")));
1956 list.push_back(std::make_pair(Gettext::keyname(_("IGNOREALL:A")),
1957 _("Ignore All")));
1958
1959 return true;
1960 }
1961
processKey(const Curses::Key & key)1962 bool CursesEditMessage::ReplacePrompt::processKey(const Curses::Key &key)
1963 {
1964 Gettext::Key *keys[10];
1965
1966 keys[0]=&key_REPLACE0;
1967 keys[1]=&key_REPLACE1;
1968 keys[2]=&key_REPLACE2;
1969 keys[3]=&key_REPLACE3;
1970 keys[4]=&key_REPLACE4;
1971 keys[5]=&key_REPLACE5;
1972 keys[6]=&key_REPLACE6;
1973 keys[7]=&key_REPLACE7;
1974 keys[8]=&key_REPLACE8;
1975 keys[9]=&key_REPLACE9;
1976
1977 size_t i;
1978
1979 for (i=0; i<10 && i<wordlist.size(); i++)
1980 {
1981 if (key == *keys[i])
1982 {
1983 replaceAction=replaceWord;
1984 replaceWord_UTF8=wordlist[i];
1985 Curses::keepgoing=false;
1986 return true;
1987 }
1988 }
1989
1990 if (key == key_REPLACE_K)
1991 {
1992 replaceAction=replaceCustom;
1993 Curses::keepgoing=false;
1994 return true;
1995 }
1996
1997 if (key == key_IGNORE_K)
1998 {
1999 replaceAction=replaceIgnore;
2000 Curses::keepgoing=false;
2001 return true;
2002 }
2003
2004 if (key == key_IGNOREALL_K)
2005 {
2006 replaceAction=replaceIgnoreAll;
2007 Curses::keepgoing=false;
2008 return true;
2009 }
2010
2011
2012 if (key == key_ABORT)
2013 {
2014 replaceAction=replaceAbort;
2015 Curses::keepgoing=false;
2016 return true;
2017 }
2018
2019 statusBar->beepError();
2020 return true;
2021 }
2022
2023 CursesEditMessage::ReplacePrompt::ReplaceAction
prompt()2024 CursesEditMessage::ReplacePrompt::prompt()
2025 {
2026 Curses::keepgoing=true;
2027 myServer::nextScreen=NULL;
2028 myServer::nextScreenArg=NULL;
2029
2030 myServer::eventloop();
2031
2032 if (myServer::nextScreen)
2033 return replaceAbort;
2034
2035 return replaceAction;
2036 }
2037
2038 //////////////////////////////////////////////////////////////////////////
2039
2040
2041 std::u32string::iterator
getIndexOfCol(std::u32string & line,size_t colNum)2042 CursesEditMessage::getIndexOfCol(std::u32string &line,
2043 size_t colNum)
2044 {
2045 widecharbuf wc;
2046
2047 size_t col=0;
2048
2049 std::u32string::iterator b(line.begin()),
2050 e(line.end()), p=b,
2051 retvec=b;
2052
2053 while (1)
2054 {
2055 if (b != e)
2056 {
2057 if (b == p || !unicode_grapheme_break(b[-1], *b))
2058 {
2059 ++b;
2060 continue;
2061 }
2062 }
2063
2064 if (b > p)
2065 {
2066 wc.init_unicode(p, b);
2067
2068 size_t grapheme_width=wc.wcwidth(col);
2069
2070 col += grapheme_width;
2071
2072 if (col > colNum)
2073 break;
2074 retvec=b;
2075 }
2076
2077 p=b;
2078
2079 if (b == e)
2080 break;
2081 }
2082
2083 return retvec;
2084 }
2085
2086 std::u32string::iterator
getIndexOfCol(std::u32string & line,std::vector<size_t> & pos,size_t colNum)2087 CursesEditMessage::getIndexOfCol(std::u32string &line,
2088 std::vector<size_t> &pos,
2089 size_t colNum)
2090 {
2091 size_t n=pos.size()-1;
2092
2093 while (n > 0)
2094 {
2095 if (pos[n] <= colNum)
2096 break;
2097 --n;
2098 }
2099
2100 return line.begin() + n;
2101
2102 #if 0
2103 std::u32string::iterator indexp=line.begin();
2104
2105 std::u32string::iterator b=line.begin(), e=line.end();
2106
2107 size_t col=0;
2108
2109 while (b != e)
2110 {
2111 if (col <= colNum)
2112 indexp=b;
2113
2114 if (*b == '\t')
2115 {
2116 col += WRAPTABSIZE;
2117 col -= (col % WRAPTABSIZE);
2118 }
2119 else
2120 col++;
2121 b++;
2122 }
2123 if (col <= colNum)
2124 indexp=e;
2125
2126 return indexp;
2127 #endif
2128 }
2129
mark()2130 void CursesEditMessage::mark()
2131 {
2132 std::u32string line;
2133
2134 getText(cursorRow, line);
2135
2136 markRow=cursorRow;
2137 markCursorPos= getIndexOfCol(line, cursorCol) - line.begin();
2138 marked=true;
2139 }
2140
end()2141 void CursesEditMessage::end()
2142 {
2143 std::u32string line;
2144
2145 getText(cursorRow, line);
2146
2147 widecharbuf wc;
2148
2149 wc.init_unicode(line.begin(), line.end());
2150
2151 cursorCol=wc.wcwidth(0);
2152 drawLine(cursorRow);
2153 }
2154
yank(const std::list<CursesFlowedLine> & yankText,const std::string & chset,bool doUpdate)2155 void CursesEditMessage::yank(const std::list<CursesFlowedLine> &yankText,
2156 const std::string &chset,
2157 bool doUpdate)
2158 {
2159 size_t origRow=cursorRow;
2160
2161 std::u32string before, after;
2162 bool flowed;
2163
2164 getTextBeforeAfter(before, after, flowed);
2165
2166 if (yankText.empty())
2167 return;
2168
2169 replaceTextLines(origRow, 1, yankText.begin(), yankText.end());
2170
2171 std::u32string line;
2172 bool pasteflowed;
2173
2174 getText(origRow, line, pasteflowed);
2175 line.insert(line.begin(), before.begin(), before.end());
2176 setText(origRow, CursesFlowedLine(line, pasteflowed));
2177
2178 getText(cursorRow, line, pasteflowed);
2179 setTextBeforeAfter(line, after, flowed);
2180 marked=false;
2181
2182 if (doUpdate)
2183 draw();
2184 }
2185
search(bool doUpdate,bool doWrap,mail::Search & searchEngine)2186 bool CursesEditMessage::search(bool doUpdate, bool doWrap,
2187 mail::Search &searchEngine)
2188 {
2189 searchEngine.reset();
2190
2191 std::u32string line;
2192
2193 getText(cursorRow, line);
2194
2195 size_t cursorPos=getIndexOfCol(line, cursorCol) - line.begin();
2196
2197 size_t searchRow=cursorRow;
2198 size_t searchPos=cursorPos;
2199
2200 size_t startRow=searchRow;
2201 size_t startPos=searchPos;
2202
2203 std::u32string lineuc;
2204
2205 // We begin the search in the middle of the line, so load a partial
2206 // line into lineuc, but make sure the cursor position doesn't change.
2207
2208 lineuc.insert(lineuc.end(), cursorPos, ' ');
2209 lineuc.insert(lineuc.end(), line.begin() + cursorPos, line.end());
2210
2211 size_t rowNum=numLines()+1;
2212 // Make sure the whole text buffer gets searched
2213
2214 bool wrappedAround=false;
2215
2216 while ( !searchEngine )
2217 {
2218 if (searchEngine.atstart())
2219 {
2220 startRow=searchRow;
2221 startPos=searchPos;
2222 }
2223
2224 if (searchPos >= lineuc.size())
2225 {
2226 searchEngine << (char32_t)' ';
2227 // Simulated whitespace
2228
2229 if (++searchRow >= numLines())
2230 {
2231 wrappedAround=true;
2232 searchRow=0;
2233 if (!doWrap)
2234 return false;
2235 }
2236
2237 lineuc.clear();
2238
2239 std::u32string uc;
2240
2241 getText(searchRow, uc);
2242
2243 lineuc.insert(lineuc.end(), uc.begin(), uc.end());
2244
2245 searchPos=0;
2246
2247 if (--rowNum == 0)
2248 {
2249 statusBar->clearstatus();
2250 statusBar->status(_("Not found"));
2251 return false;
2252 }
2253 }
2254 else
2255 {
2256 searchEngine << lineuc[searchPos];
2257 ++searchPos;
2258 }
2259 }
2260
2261 // Found the search string, select it
2262
2263 if (wrappedAround)
2264 {
2265 statusBar->clearstatus();
2266 statusBar->status(_("Wrapped around from the beginning"));
2267 }
2268
2269 marked=true;
2270 markRow=startRow;
2271 markCursorPos=startPos;
2272
2273 cursorRow=searchRow;
2274
2275 getText(cursorRow, line);
2276
2277 {
2278 widecharbuf wc;
2279
2280 wc.init_unicode(line.begin(),
2281 searchPos < line.size() ?
2282 line.begin()+searchPos:line.end());
2283 cursorCol=wc.wcwidth(0);
2284 }
2285
2286 int dummy1, dummy2;
2287 getCursorPosition(dummy1, dummy2); // Make sure on screen
2288 if (doUpdate)
2289 draw();
2290 return true;
2291 }
2292
2293
getMarkedRegion(size_t & row1,size_t & row2,size_t & pos1,size_t & pos2)2294 bool CursesEditMessage::getMarkedRegion(size_t &row1, size_t &row2,
2295 size_t &pos1, size_t &pos2)
2296 {
2297 bool swapped=false;
2298
2299 row1=markRow;
2300 pos1=markCursorPos;
2301
2302 std::u32string before, after;
2303
2304 getTextBeforeAfter(before, after);
2305
2306 row2=cursorRow;
2307 pos2=before.size();
2308
2309 if (row2 < row1 || (row2 == row1 && pos2 < pos1))
2310 {
2311 size_t swap;
2312
2313 swap=row1;
2314 row1=row2;
2315 row2=swap;
2316
2317 swap=pos1;
2318 pos1=pos2;
2319 pos2=swap;
2320
2321 swapped=true;
2322 }
2323
2324 return swapped;
2325 }
2326
deleteChar(bool is_clreol_key)2327 void CursesEditMessage::deleteChar(bool is_clreol_key)
2328 {
2329 modified();
2330
2331 if (marked)
2332 {
2333 // Delete currently selected region.
2334
2335 marked=false;
2336
2337 size_t row1, pos1, row2, pos2;
2338
2339 getMarkedRegion(row1, row2, pos1, pos2);
2340
2341 if (row1 != row2 || pos1 != pos2)
2342 {
2343 erase();
2344
2345 // save text in the cut buffer
2346
2347 if (!is_clreol_key)
2348 CursesField::cutBuffer.clear();
2349
2350 size_t i;
2351
2352 CursesFlowedLine line1, line2;
2353 std::u32string u;
2354
2355 for (i=row1; i <= row2; i++)
2356 {
2357 getText(i, line1);
2358
2359 unicode::iconvert::convert(line1.text, "utf-8", u);
2360
2361 if (i == row2)
2362 u.erase(u.begin() + pos2, u.end());
2363
2364 if (i == row1)
2365 u.erase(u.begin(), u.begin() + pos1);
2366
2367 line1.text=unicode::iconvert::convert(u, "utf-8");
2368
2369 if (is_clreol_key)
2370 {
2371 if (CursesField::cutBuffer.empty())
2372 CursesField::cutBuffer
2373 .push_back(line1);
2374
2375 CursesField::cutBuffer.back()=line1;
2376 }
2377 else
2378 CursesField::cutBuffer.push_back(line1);
2379 }
2380
2381 // Get the first and the last line in the cut region
2382 // Splice the before and after text, together.
2383
2384 std::u32string uline1, uline2;
2385
2386 getText(row1, line1);
2387 getText(row2, line2);
2388
2389 unicode::iconvert::convert(line1.text, "utf-8", uline1);
2390 unicode::iconvert::convert(line2.text, "utf-8", uline2);
2391
2392 line1.flowed=line2.flowed;
2393
2394 uline1.erase(uline1.begin() + pos1, uline1.end());
2395
2396 widecharbuf line1wc;
2397
2398 line1wc.init_unicode(uline1.begin(), uline1.end());
2399
2400 uline1.insert(uline1.end(),
2401 uline2.begin() + pos2, uline2.end());
2402
2403 line1.text=unicode::iconvert::convert(uline1, "utf-8");
2404
2405 replaceTextLines(row1, row2+1-row1,
2406 &line1, &line1+1);
2407
2408 cursorRow=row1;
2409 cursorCol=line1wc.wcwidth(0);
2410
2411 draw();
2412
2413 int dummy1, dummy2;
2414 getCursorPosition(dummy1, dummy2);
2415 return;
2416 }
2417 }
2418
2419 // Delete grapheme under cursor
2420
2421 std::u32string before, after;
2422 bool flowed;
2423
2424 getTextBeforeAfter(before, after, flowed);
2425
2426 if (is_clreol_key)
2427 CursesField::cutBuffer.push_back(CursesFlowedLine("",
2428 flowed));
2429
2430 widecharbuf wc;
2431
2432 wc.init_unicode(after.begin(), after.end());
2433
2434 if (wc.graphemes.size() > 0) // Cursor in the middle of the line.
2435 {
2436 after=wc.get_unicode_substring(1, wc.graphemes.size()-1);
2437 setTextBeforeAfter(before, after, flowed);
2438 drawLine(cursorRow);
2439 return;
2440 }
2441
2442 erase();
2443 // Cursor at the end of the line
2444
2445 if (cursorRow+1 < numLines())
2446 {
2447 // Another row below, splice it into this row.
2448
2449 size_t save_row=cursorRow;
2450
2451 CursesFlowedLine l;
2452
2453 getText(save_row+1, l);
2454
2455 unicode::iconvert::convert(l.text, "utf-8", after);
2456
2457 replaceTextLines(save_row+1, 1, &l, &l);
2458
2459 cursorRow=save_row;
2460 setTextBeforeAfter(before, after, l.flowed);
2461 }
2462 draw();
2463 }
2464
2465 // Inserted text, see if the line needs to be wrapped.
2466
inserted()2467 void CursesEditMessage::inserted()
2468 {
2469 size_t row=cursorRow;
2470
2471 editablewidechar current_buffer;
2472 bool wrappedLine;
2473
2474 {
2475 std::u32string before, after;
2476
2477 getTextBeforeAfter(before, after, wrappedLine);
2478
2479 current_buffer.set_contents(before, after);
2480 }
2481
2482 unicode_wordwrap_oiterator insert_iter;
2483
2484 {
2485 std::u32string line;
2486
2487 current_buffer.before_insert.tounicode(line);
2488
2489 unicodewordwrap(line.begin(),
2490 line.end(),
2491 unicoderewrapnone(),
2492 insert_iter,
2493 LINEW, false);
2494 }
2495
2496 if (insert_iter.wrapped.size() > 1)
2497 {
2498 std::list<CursesFlowedLine>::iterator
2499 lastLine=--insert_iter.wrapped.end();
2500
2501 replaceTextLines(row, 0,
2502 insert_iter.wrapped.begin(), lastLine);
2503
2504 ++cursorRow;
2505
2506 std::u32string before, after;
2507
2508 current_buffer.get_contents(before, after);
2509
2510 before.clear();
2511 unicode::iconvert::convert(lastLine->text, "utf-8", before);
2512
2513 setTextBeforeAfter(before, after, wrappedLine);
2514 cursorLineHorizShift=0;
2515 draw();
2516 }
2517 else
2518 drawLine(row);
2519 }
2520
left(bool moveOff)2521 void CursesEditMessage::left(bool moveOff)
2522 {
2523 std::u32string line;
2524
2525 getText(cursorRow, line);
2526
2527 std::u32string::iterator beg(line.begin()),
2528 cursorPos=getIndexOfCol(line, cursorCol);
2529
2530 if (cursorPos == line.begin())
2531 {
2532 if (cursorRow == 0)
2533 {
2534 if (moveOff)
2535 transferPrevFocus();
2536 }
2537 else
2538 {
2539 --cursorRow;
2540 end();
2541 drawLine(cursorRow+1);
2542 return;
2543 }
2544 }
2545 else
2546 {
2547 do
2548 {
2549 --cursorPos;
2550 } while (cursorPos > beg &&
2551 !unicode_grapheme_break(cursorPos[-1], *cursorPos));
2552
2553 widecharbuf wc;
2554
2555 wc.init_unicode(line.begin(), cursorPos);
2556
2557 cursorCol=wc.wcwidth(0);
2558 }
2559
2560 drawLine(cursorRow);
2561 }
2562
right()2563 void CursesEditMessage::right()
2564 {
2565 std::u32string line;
2566
2567 getText(cursorRow, line);
2568
2569 std::u32string::iterator
2570 cursorPos=getIndexOfCol(line, cursorCol), end(line.end());
2571
2572 if (cursorPos == line.end())
2573 {
2574 if (cursorRow + 1 < numLines())
2575 {
2576 cursorCol=0;
2577 cursorLineHorizShift=0;
2578 ++cursorRow;
2579 drawLine(cursorRow - 1);
2580 }
2581 }
2582 else
2583 {
2584 do
2585 {
2586 ++cursorPos;
2587
2588 } while (cursorPos < end &&
2589 !unicode_grapheme_break(cursorPos[-1], *cursorPos));
2590
2591 widecharbuf wc;
2592
2593 wc.init_unicode(line.begin(), cursorPos);
2594
2595 cursorCol=wc.wcwidth(0);
2596 }
2597 drawLine(cursorRow);
2598 }
2599
getCursorPosition(int & row,int & col)2600 int CursesEditMessage::getCursorPosition(int &row, int &col)
2601 {
2602 std::u32string uc_expanded;
2603
2604 getText(cursorRow, uc_expanded);
2605
2606 size_t w=getWidth();
2607
2608 if (w < 1)
2609 w=1;
2610
2611 size_t virtual_horiz_pos=getIndexOfCol(uc_expanded, cursorCol) -
2612 uc_expanded.begin();
2613 size_t virtual_horiz_col_adjusted;
2614
2615 widecharbuf virtual_line;
2616
2617 virtual_line.init_unicode(uc_expanded.begin(),
2618 uc_expanded.begin()+virtual_horiz_pos);
2619
2620 virtual_horiz_col_adjusted=virtual_line.wcwidth(0);
2621
2622 virtual_line.init_unicode(uc_expanded.begin(), uc_expanded.end());
2623
2624 size_t virtual_width=virtual_line.wcwidth(0);
2625
2626 if (cursorLineHorizShift >= virtual_horiz_pos)
2627 {
2628 size_t new_shift_pos=virtual_horiz_pos;
2629
2630 while (new_shift_pos > 0)
2631 {
2632 --new_shift_pos;
2633
2634 if (new_shift_pos > 0 &&
2635 unicode_grapheme_break(uc_expanded[new_shift_pos-1],
2636 uc_expanded[new_shift_pos]))
2637 break;
2638 }
2639
2640 if (new_shift_pos != cursorLineHorizShift)
2641 {
2642 cursorLineHorizShift=new_shift_pos;
2643 drawLine(cursorRow);
2644 }
2645 }
2646
2647 if (virtual_width < w)
2648 {
2649 if (cursorLineHorizShift > 0)
2650 {
2651 cursorLineHorizShift=0;
2652 drawLine(cursorRow);
2653 }
2654 }
2655 else
2656 {
2657 size_t cursor_grapheme_width=1;
2658
2659 if (virtual_horiz_pos < uc_expanded.size())
2660 {
2661 size_t p=virtual_horiz_pos;
2662
2663 do
2664 {
2665 ++p;
2666 } while (p < uc_expanded.size() &&
2667 !unicode_grapheme_break(uc_expanded[p-1],
2668 uc_expanded[p]));
2669
2670 widecharbuf wc;
2671
2672 wc.init_unicode(uc_expanded.begin()+virtual_horiz_pos,
2673 uc_expanded.begin()+p);
2674
2675 cursor_grapheme_width=
2676 wc.wcwidth(virtual_horiz_col_adjusted);
2677 }
2678
2679 size_t col_after_cursor=virtual_horiz_col_adjusted +
2680 cursor_grapheme_width;
2681
2682 size_t minimum_col_shift=
2683 col_after_cursor >= (w-1) ? col_after_cursor-(w-1):0;
2684
2685 size_t minimum_horiz_shift=getIndexOfCol(uc_expanded,
2686 minimum_col_shift)-
2687 uc_expanded.begin();
2688
2689 while (minimum_horiz_shift < uc_expanded.size())
2690 {
2691 widecharbuf wc;
2692
2693 wc.init_unicode(uc_expanded.begin(),
2694 uc_expanded.begin()
2695 +minimum_horiz_shift);
2696
2697 minimum_col_shift=wc.wcwidth(0);
2698
2699 if (minimum_col_shift+(w-1) >= col_after_cursor)
2700 break;
2701
2702 while (++minimum_horiz_shift < uc_expanded.size())
2703 {
2704 if (unicode_grapheme_break
2705 (uc_expanded[minimum_horiz_shift-1],
2706 uc_expanded[minimum_horiz_shift]))
2707 break;
2708 }
2709 }
2710
2711 if (cursorLineHorizShift < minimum_horiz_shift)
2712 {
2713 cursorLineHorizShift=minimum_horiz_shift;
2714 drawLine(cursorRow);
2715 }
2716 }
2717
2718 row=cursorRow;
2719
2720 size_t virtual_horiz_shift_pos;
2721
2722 {
2723 widecharbuf wc;
2724
2725 wc.init_unicode(uc_expanded.begin(),
2726 uc_expanded.begin() + cursorLineHorizShift);
2727
2728 virtual_horiz_shift_pos=wc.wcwidth(0);
2729 }
2730
2731 col=virtual_horiz_col_adjusted - virtual_horiz_shift_pos;
2732
2733 Curses::getCursorPosition(row, col);
2734
2735 if (marked)
2736 {
2737 size_t row1, col1, row2, col2;
2738
2739 if (getMarkedRegion(row1, row2, col1, col2))
2740 return 0;
2741 }
2742
2743 return 1;
2744 }
2745
draw()2746 void CursesEditMessage::draw()
2747 {
2748 size_t firstRow, nrows;
2749
2750 getVerticalViewport(firstRow, nrows);
2751
2752 size_t i;
2753
2754 for (i=0; i<nrows; i++)
2755 drawLine(firstRow + i);
2756 }
2757
erase()2758 void CursesEditMessage::erase()
2759 {
2760 size_t firstRow, nrows;
2761
2762 getVerticalViewport(firstRow, nrows);
2763
2764 size_t i;
2765
2766 std::u32string spaces;
2767
2768 spaces.insert(spaces.begin(), getWidth(), ' ');
2769
2770 for (i=0; i<nrows; i++)
2771 writeText(spaces, i+firstRow, 0, CursesAttr());
2772 }
2773
enterKey()2774 void CursesEditMessage::enterKey()
2775 {
2776 size_t row=cursorRow;
2777
2778 std::u32string currentLine, nextLine;
2779 bool flowed;
2780
2781 getTextBeforeAfter(currentLine, nextLine, flowed);
2782
2783 CursesFlowedLine newlines[2];
2784
2785 newlines[0]=CursesFlowedLine(unicode::iconvert::convert(currentLine,
2786 "utf-8"), false);
2787 newlines[1]=CursesFlowedLine(unicode::iconvert::convert(nextLine, "utf-8"),
2788 flowed);
2789
2790 replaceTextLines(cursorRow, 1, newlines, newlines+2);
2791 cursorRow=row+1;
2792 cursorCol=0;
2793 }
2794
2795 // Draw the indicated line.
2796
drawLine(size_t lineNum)2797 void CursesEditMessage::drawLine(size_t lineNum)
2798 {
2799 std::u32string chars;
2800 bool wrapped;
2801
2802 {
2803 CursesFlowedLine line;
2804
2805 getText(lineNum, line);
2806
2807 unicode::iconvert::convert(line.text, "utf-8", chars);
2808 wrapped=line.flowed;
2809 }
2810
2811 size_t w=getWidth();
2812
2813 //
2814 // Compute selectedFirst-selectedLast range to show in inverse video
2815
2816 size_t selectedFirst=0; // First char to highlight
2817 size_t selectedLast=0; // First char to highlight no more
2818
2819 if (marked)
2820 {
2821 size_t row1, col1;
2822 size_t row2, col2;
2823
2824 getMarkedRegion(row1, row2, col1, col2);
2825
2826 if (row1 <= lineNum && lineNum <= row2)
2827 {
2828 if (row1 == lineNum)
2829 selectedFirst=col1;
2830
2831 if (row2 == lineNum)
2832 selectedLast=col2;
2833 else
2834 selectedLast=chars.size();
2835 }
2836 }
2837
2838 /*
2839 ** If cursor leaves the edit area we don't want to leave the
2840 ** text display shifted, UNLESS the cursor is on the status line.
2841 */
2842
2843 bool show_line_shifted=lineNum==cursorRow && (hasFocus() ||
2844 statusBar->prompting());
2845
2846 bool past_right_margin=false;
2847
2848 widecharbuf shiftedsel; // Stuff shifted past the left margin
2849
2850 if (show_line_shifted && cursorLineHorizShift > 0)
2851 {
2852 if (cursorLineHorizShift > chars.size())
2853 cursorLineHorizShift=chars.size(); // Sanity check
2854
2855 // Move the shifted contents from chars into shiftedsel
2856
2857 shiftedsel.init_unicode(chars.begin(),
2858 chars.begin() + cursorLineHorizShift);
2859
2860 chars.erase(chars.begin(),
2861 chars.begin() + cursorLineHorizShift);
2862
2863 // Update highlighted range to account for the shift.
2864
2865 if (selectedFirst > cursorLineHorizShift)
2866 selectedFirst -= cursorLineHorizShift;
2867 else
2868 selectedFirst=0;
2869
2870 if (selectedLast > cursorLineHorizShift)
2871 selectedLast -= cursorLineHorizShift;
2872 else
2873 selectedLast=0;
2874 }
2875
2876 // Sanity check on the highlighted range
2877
2878 if (selectedFirst > chars.size())
2879 selectedFirst=chars.size();
2880
2881 if (selectedLast > chars.size())
2882 selectedLast=chars.size();
2883
2884 // Split the contents into separate before/selection/after parts.
2885
2886 widecharbuf beforesel, sel, aftersel;
2887
2888 beforesel.init_unicode(chars.begin(), chars.begin() + selectedFirst);
2889 sel.init_unicode(chars.begin() + selectedFirst,
2890 chars.begin() + selectedLast);
2891 aftersel.init_unicode(chars.begin() + selectedLast, chars.end());
2892
2893 // Perform tab expansion
2894
2895 {
2896 size_t w=shiftedsel.expandtabs(0);
2897
2898 w = beforesel.expandtabs(w);
2899 w = sel.expandtabs(w);
2900 aftersel.expandtabs(w);
2901 }
2902
2903 // Truncate to right margin
2904
2905 size_t pos=0;
2906
2907 if (beforesel.wcwidth(0) + pos > w)
2908 {
2909 std::pair<std::u32string, size_t>
2910 res=beforesel.get_unicode_truncated(w-pos, pos);
2911
2912 beforesel.init_unicode(res.first.begin(), res.first.end());
2913 }
2914
2915 pos += beforesel.wcwidth(0);
2916
2917 if (sel.wcwidth(pos) + pos > w)
2918 {
2919 std::pair<std::u32string, size_t>
2920 res=sel.get_unicode_truncated(w-pos, pos);
2921
2922 sel.init_unicode(res.first.begin(), res.first.end());
2923 }
2924
2925 pos += sel.wcwidth(pos);
2926
2927 if (aftersel.wcwidth(pos) + pos > w)
2928 {
2929 std::pair<std::u32string, size_t>
2930 res=aftersel.get_unicode_truncated(w-pos, pos);
2931
2932 aftersel.init_unicode(res.first.begin(), res.first.end());
2933 past_right_margin=true;
2934 }
2935
2936 pos += aftersel.wcwidth(pos);
2937
2938 // Now, re-extract all the pieces, and set their horizontal positions
2939
2940 CursesAttr attr, rev, rwrapattr;
2941
2942 rwrapattr.setFgColor(color_md_headerName.fcolor);
2943
2944 rev.setReverse();
2945
2946 std::u32string larr_shown, rarr_shown, rwrap_shown;
2947 size_t before_pos, sel_pos, after_pos;
2948 size_t rarr_pos=w;
2949
2950 // Ignore left-right shift indications, for now.
2951
2952 before_pos=0;
2953 sel_pos=before_pos + beforesel.wcwidth(before_pos);
2954 after_pos=sel_pos + sel.wcwidth(sel_pos);
2955
2956 if (wrapped)
2957 rwrap_shown.push_back(ucwrap);
2958
2959 if (show_line_shifted)
2960 {
2961 if (cursorLineHorizShift > 0)
2962 {
2963 // Shifted past the left margin. Find the first buffer
2964 // that contains at least one grapheme
2965
2966 widecharbuf *ptr=&beforesel;
2967 size_t *posptr=&before_pos;
2968
2969 if (ptr->ustring.empty())
2970 {
2971 ptr=&sel;
2972 posptr=&sel_pos;
2973 }
2974
2975 if (ptr->ustring.empty())
2976 {
2977 ptr=&aftersel;
2978 posptr=&after_pos;
2979 }
2980
2981 size_t first_grapheme_width=1;
2982
2983 if (!ptr->ustring.empty())
2984 {
2985 // Remove first grapheme from the buffer,
2986 // and adjust its position, accordingly.
2987
2988 first_grapheme_width=ptr->graphemes
2989 .begin()->wcwidth(*posptr);
2990
2991 ptr->init_string(ptr->
2992 get_substring(1,
2993 ptr->graphemes
2994 .size()-1));
2995 *posptr=first_grapheme_width;
2996 }
2997
2998 larr_shown.push_back(ularr);
2999 }
3000
3001 if (past_right_margin)
3002 {
3003 // Shifted past the right margin. Find the last
3004 // buffer with at least one grapheme.
3005
3006 widecharbuf *ptr=&aftersel;
3007 size_t *posptr=&after_pos;
3008
3009 if (ptr->ustring.empty())
3010 {
3011 ptr=&sel;
3012 posptr=&sel_pos;
3013 }
3014
3015 if (ptr->ustring.empty())
3016 {
3017 ptr=&beforesel;
3018 posptr=&before_pos;
3019 }
3020
3021 // Truncate the buffer
3022
3023 rarr_shown.push_back(urarr);
3024 rarr_pos=w-1;
3025 ptr->init_string(ptr->get_string_truncated(rarr_pos
3026 -*posptr,
3027 *posptr)
3028 .first);
3029 }
3030 }
3031
3032 std::u32string
3033 before_shown, sel_shown, after_shown;
3034 beforesel.tounicode(before_shown);
3035 sel.tounicode(sel_shown);
3036 after_shown=aftersel.get_unicode_fixedwidth((rarr_pos >= after_pos
3037 ? rarr_pos-after_pos:0),
3038 after_pos);
3039
3040 if (!rwrap_shown.empty() && !after_shown.empty() &&
3041 after_shown.back() == ' ')
3042 after_shown.pop_back(); // Make room for the wrap indication
3043
3044 if (!larr_shown.empty())
3045 writeText(larr_shown, lineNum, 0, rev);
3046
3047 if (!rwrap_shown.empty())
3048 writeText(rwrap_shown, lineNum, w-1, rwrapattr);
3049
3050 if (!before_shown.empty())
3051 writeText(before_shown, lineNum, before_pos, attr);
3052
3053 if (!sel_shown.empty())
3054 writeText(sel_shown, lineNum, sel_pos, rev);
3055
3056 if (!after_shown.empty())
3057 writeText(after_shown, lineNum, after_pos, attr);
3058
3059 if (!rarr_shown.empty())
3060 writeText(rarr_shown, lineNum, rarr_pos, rev);
3061 }
3062
load(std::istream & i,bool isflowed,bool delsp)3063 void CursesEditMessage::load(std::istream &i, bool isflowed, bool delsp)
3064 {
3065 get_file_helper beg_iter(i, isflowed, delsp), end_iter;
3066
3067 replaceTextLines(0, numLines(), beg_iter, end_iter);
3068 cursorRow=0;
3069 draw();
3070 }
3071
save(std::ostream & o,bool isflowed)3072 void CursesEditMessage::save(std::ostream &o, bool isflowed)
3073 {
3074 for (size_t i=0, n=numLines(); i<n; ++i)
3075 {
3076 o << unicode::iconvert::convert(getUTF8Text(i, isflowed), "utf-8",
3077 unicode_default_chset())
3078 << std::endl;
3079 }
3080 }
3081
processKey(const Curses::Key & key)3082 bool CursesEditMessage::processKey(const Curses::Key &key)
3083 {
3084 return false;
3085 }
3086
listKeys(std::vector<std::pair<std::string,std::string>> & list)3087 bool CursesEditMessage::listKeys( std::vector< std::pair<std::string, std::string> > &list)
3088 {
3089 list.push_back(std::make_pair(Gettext::keyname(_("MARK_K:^ ")),
3090 _("Mark")));
3091 list.push_back(std::make_pair(Gettext::keyname(_("JUSTIFY_K:^J")),
3092 _("Justify")));
3093 list.push_back(std::make_pair(Gettext::keyname(_("CLREOL_K:^K")),
3094 _("Line Clear")));
3095 list.push_back(std::make_pair(Gettext::keyname(_("SEARCH_K:^S")),
3096 _("Search")));
3097 list.push_back(std::make_pair(Gettext::keyname(_("REPLACE_K:^R")),
3098 _("Srch/Rplce")));
3099 list.push_back(std::make_pair(Gettext::keyname(_("CUT_K:^W")),
3100 _("Cut")));
3101 list.push_back(std::make_pair(Gettext::keyname(_("YANK_K:^Y")),
3102 _("Paste")));
3103 list.push_back(std::make_pair(Gettext::keyname(_("INSERT_K:^G")),
3104 _("Insert File")));
3105 list.push_back(std::make_pair(Gettext::keyname(_("DICTSPELL_K:^D")),
3106 _("Dict Spell")));
3107 list.push_back(std::make_pair(Gettext::keyname(_("CANCEL_K:^C")),
3108 _("Cancel/Exit")));
3109
3110 if (Macros::getRuntimeMacros() != NULL)
3111 {
3112 list.push_back(std::make_pair(Gettext::keyname(_("MACRO_K:^N")),
3113 _("New/Del Macro")));
3114 }
3115
3116 if (externalEditor.size() > 0)
3117 {
3118 list.push_back(std::make_pair(Gettext::keyname(_("EDITOR_K:^U")),
3119 _("Ext Editor")));
3120 }
3121 return false;
3122 }
3123
getTextHorizPos(const std::u32string & line,size_t column)3124 size_t CursesEditMessage::getTextHorizPos(const std::u32string &line,
3125 size_t column)
3126 {
3127 widecharbuf wc;
3128
3129 wc.init_unicode(line.begin(), line.begin()+column);
3130
3131 return wc.wcwidth(0);
3132 }
3133