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> &macroMap=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