summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTzafrir Cohen <tzafrir@cohens.org.il>2012-09-07 15:14:04 +0300
committerTzafrir Cohen <tzafrir@cohens.org.il>2012-09-07 15:14:04 +0300
commitd4d16198c2924b1085258c0b6562b562c7df3c29 (patch)
treed160aabd972fee403621b84cf1fc7848e98d45b9
-rw-r--r--AUTHORS5
-rw-r--r--COPYING340
-rw-r--r--INSTALL114
-rw-r--r--MANUAL.he1165
-rw-r--r--Makefile.am78
-rw-r--r--Makefile.in573
-rw-r--r--NEWS108
-rw-r--r--README58
-rw-r--r--THANKS13
-rw-r--r--TODO58
-rw-r--r--aclocal.m4127
-rw-r--r--basemenu.cc552
-rw-r--r--basemenu.h129
-rw-r--r--bidi.cc136
-rw-r--r--bidi.h122
-rw-r--r--bindings.cc510
-rw-r--r--config.h.in75
-rwxr-xr-xconfigure3518
-rw-r--r--configure.in316
-rw-r--r--converters.cc295
-rw-r--r--converters.h102
-rw-r--r--dbg.cc64
-rw-r--r--dbg.h39
-rw-r--r--dialogline.cc178
-rw-r--r--dialogline.h76
-rw-r--r--directvect.h96
-rw-r--r--dispatcher.h126
-rw-r--r--editbox.cc1909
-rw-r--r--editbox.h826
-rw-r--r--editbox2.cc1759
-rw-r--r--editor.cc1103
-rw-r--r--editor.h232
-rw-r--r--event.cc189
-rw-r--r--event.h88
-rw-r--r--helpbox.cc248
-rw-r--r--helpbox.h51
-rw-r--r--inputline.cc443
-rw-r--r--inputline.h147
-rwxr-xr-xinstall-sh251
-rw-r--r--io.cc470
-rw-r--r--io.h48
-rw-r--r--iso88598.cc111
-rw-r--r--iso88598.h26
-rw-r--r--kbdtab46
-rw-r--r--label.cc69
-rw-r--r--label.h46
-rw-r--r--main.cc820
-rw-r--r--menus.cc798
-rw-r--r--menus.h12
-rwxr-xr-xmissing198
-rw-r--r--mk_wcwidth.cc296
-rw-r--r--mk_wcwidth.h9
-rwxr-xr-xmkinstalldirs40
-rw-r--r--my_wctob.h44
-rw-r--r--pathnames.h37
-rw-r--r--pgeresh.in16
-rw-r--r--point.h61
-rw-r--r--question.cc46
-rw-r--r--question.h40
-rw-r--r--reprtab121
-rw-r--r--scrollbar.cc96
-rw-r--r--scrollbar.h46
-rw-r--r--shaping.cc368
-rw-r--r--shaping.h30
-rw-r--r--speller.cc862
-rw-r--r--speller.h117
-rw-r--r--stamp-h.in0
-rw-r--r--statusline.cc168
-rw-r--r--statusline.h111
-rw-r--r--terminal.cc213
-rw-r--r--terminal.h80
-rw-r--r--themes.cc564
-rw-r--r--themes.h69
-rw-r--r--themes/README.themes86
-rw-r--r--themes/arnold.thm43
-rw-r--r--themes/borland.thm34
-rw-r--r--themes/default.thm37
-rw-r--r--themes/default_bw.thm40
-rw-r--r--themes/green.thm34
-rw-r--r--themes/mc.thm39
-rw-r--r--themes/occult.thm50
-rw-r--r--transtab162
-rw-r--r--transtbl.cc136
-rw-r--r--transtbl.h45
-rw-r--r--types.cc209
-rw-r--r--types.h127
-rw-r--r--undo.cc183
-rw-r--r--undo.h99
-rw-r--r--univalues.h102
-rw-r--r--utf8.cc149
-rw-r--r--utf8.h26
-rw-r--r--widget.cc118
-rw-r--r--widget.h94
93 files changed, 24110 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..b056e88
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Mooffie <mooffie@typo.co.il>
+ * Primary author.
+
+Markus Kuhn <Markus.Kuhn@cl.cam.ac.uk>
+ * Free wcwidth() implementation.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..1f18450
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,114 @@
+REQUIREMENTS
+------------
+
+The minimum requirements for installing Geresh, besides a standard C++
+compiler, are the following two libraries:
+
+ * FriBiDi
+
+ Available at:
+ <http://fribidi.sourceforge.net>
+
+ * curses (or ncurses)
+
+However, you'll probably want to have two additional libraries which Geresh
+optionally use:
+
+ * ncursesw
+
+ This is curses with wchar_t support, as outlined in the X/Open
+ standard.
+
+ THIS LIBRARY IS MANDATORY FOR RUNNING GERESH IN THE UTF-8 LOCALE.
+
+ Available at:
+
+ <http://www.gnu.org/software/ncurses/ncurses.html>
+
+ You MUST configure it with "--enable-widec", or else it won't
+ generate the appropriate libraries (libncursesw.so, etc).
+
+ * iconv
+
+ If you want Geresh to recognize a broad range of encodings when it
+ loads and saves files, make sure your system has the iconv
+ functions.
+
+ Contemporary glibc libraries have the iconv implementation
+ built-in, but if you're using an older system, or a non-glibc
+ system, you can install libiconv separately:
+
+ <http://www.gnu.org/software/libiconv/>
+
+IMPORTANT: If you want to run Geresh in the UTF-8 locale, you must install
+ncursesw. If you don't compile Geresh against ncursesw then it will print an
+error message and abort when it finds out it's running in the UTF-8 locale.
+
+INSTALLING
+----------
+
+Like most packages nowadays, Geresh comes with a "configure" script. This
+script tries to automatically determine your system capabilities. Type
+"configure --help" to learn more. In most cases you'll just do
+"./configure", then "make", then "make install" (the latter as root).
+
+When "configure" finishes it prints a short summary of what it has found on
+your system. A sample printout:
+
+ "
+ Results:
+ --------
+ curses library: ncursesw
+ use iconv: yes
+ default file encoding: CP1255
+ (debugging support: no)
+ "
+Please pay attention to what "configure" prints. In particular, note the "w"
+in "ncursesw". If "configure" doesn't find ncursesw, it configures Geresh to
+use ncurses or plain curses, and prints a warning saying you won't be able
+to run Geresh in the UTF-8 locale.
+
+PROBLEMS
+--------
+
+Please email me if you encounter any problems installing Geresh.
+
+TESTING
+-------
+
+When you start Geresh you may see question marks or gibberish instead of
+Hebrew characters. There may be three reasons for that:
+
+1. You're using a ISO-8859-{1,15} or other locale (like "POSIX") in which
+ Hebrew characters do not exist (solution: either change the locale or
+ use the "--iso88598-term" option); or
+2. Your screen font doesn't have Hebrew glyphs; or:
+3. The locale (e.g. iso-8859-x) and the terminal (e.g. UTF-8) disagree about
+ the encoding. For example, if you see lots of "x"s printed, it probably
+ means you're in the UTF-8 locale, but your terminal was not sent the
+ 'unicode_start' escape sequence.
+
+INSTALLING FRIBIDI AND NCURSESW LOCALLY
+---------------------------------------
+
+You don't have to have root permissions to install ncursesw and/or fribidi.
+You can install them in your home directory, say "/home/mooffie/local".
+Then, to configure Geresh, type:
+
+$ export FRIBIDI_CONFIG=/path/to/fribidi-config
+$ ./configure --with-curses=/home/mooffie/local
+
+SUPPORTED PLATFORMS
+-------------------
+
+Geresh has been compiled and tested under the following UNIX-like operating
+systems:
+
+[x86] Linux RedHat 7.3 (Kernel 2.4)
+[x86] FreeBSD Release 4.6
+[x86] Linux Mandrake 8.3 (Kernel 2.4)
+[x86] Cygwin (Windows 98)
+
+(Some linking tests the configure script does failed on Cygwin, but that's
+probably a problem with my own system.)
+
diff --git a/MANUAL.he b/MANUAL.he
new file mode 100644
index 0000000..45e1d82
--- /dev/null
+++ b/MANUAL.he
@@ -0,0 +1,1165 @@
+$Id: MANUAL.he,v 1.15 2004/10/02 20:36:43 mooffie Exp $
+
+גרש
+
+עורך טקסט פשוט
+
+גרסה 0.6.3
+
+מדריך למשתמש
+
+מופי <mooffie@typo.co.il>
+
+גרסת HTML קלה לניווט של מדריך זה ניתן למצוא באתר של גרש:
+
+<http://www.typo.co.il/~mooffie/geresh/>
+
+תוכן העניינים
+‾‾‾‾‾‾‾‾‾‾‾‾‾
+- היכרות
+- התפריט וקיצורי המקשים
+- עזרה
+- קידודים
+ - קידוד המסוף
+ - מסופי UTF-8
+ - מסופי 8 ביט
+ - תווי מסגרת גראפיים
+ - קידוד הקבצים
+ - טעינת קובץ
+ - שמירת קובץ
+ - אודות ICONV
+ - מסקנות לגבי שמירה וטעינה של קבצים
+ - שמות קבצים המכילים תווים שאינם ASCII
+- נוהל שמירת קובץ
+ - גיבוי
+ - שמירת חירום בעת קבלת אירוע SIGHUP
+- עוד על תווים
+ - הזנת תווים שאינם נמצאים על המקלדת
+ - מפות תווים
+ - מידע על תווים
+ - סופי שורות
+- המסך
+ - שורת הסטאטוס
+ - שורת הדיאלוג
+ - שפת הדיאלוג
+- עריכת טקסט דו־כיווני
+ - תווי פורמט של BiDi
+ - ביטול זמני של אלגוריתם ה־BiDi
+ - תנועת סמן לוגית ותנועת סמן ויזואלית
+ - אלגוריתם לקביעת הכיווניות
+- עברית
+ - ניקוד עברי
+ - מקף
+ - התווים גרש, גרשיים ומרכאות
+- ערבית
+ - חיבור אותיות
+ - ניקוד ערבי
+- המלצה: גופן
+- המלצה: גופן נוסף
+- המלצה: תוכנת אמולצית מסוף
+ - מספר מלים על konsole
+- חרטה (undo)
+- גלילה
+ - פס הגלילה
+- קיפול פסקאות (wrap)
+- יישור אוטומטי
+- בדיקת איות
+ - עבודה עם המאיית
+ - טעינה והסרה של המאיית
+ - המאיית העברי hspell
+- צביעה תחבירית
+ - הדגשת *טקסט* ו_טקסט_
+- ערכות צבעים
+- תקשורת עם תוכנות אחרות
+ - הפעלת עורך חיצוני
+ - הפעלת עורך חיצוני: מידע נוסף
+ - צינורות
+ - ה־clipboard של X11
+ - גרש כ־pager
+ - הפקת פלט בעברית ויזואלית
+ - קו תחתי בפלט הויזואלי
+- הקובץ gereshrc
+
+היכרות
+‾‾‾‾‾‾
+גרש הנו עורך טקסט רב־לשוני פשוט, עם תמיכה ב־Unicode, ב־BiDi וב־Arabic shaping, למסופי טקסט.
+
+מוזר לדבר על תכנת מָסוֹף בשנת 2004 כאשר נדמה שכולם עברו להשתמש ביישומי GUI. למעשה, גם מוזר להשתמש במלה "עברו", כי כיום אנשים "נולדו" לתוך GUI ומעולם לא חוו את מסך הטקסט הפשוט. אבל למרות שתכנות מסוגו של גרש, תכנות מָסוֹף, אינן פופולריות, עדיין יש להן מקום, ובעיקר הודות לדרישות המערכת המועטות.
+
+גרש לא בא להחליף את emacs או את vi, אך הוא מציע מספיק כדי לאפשר את ביצוען הנוח של מטלות עריכה פשוטות.
+
+בכתיבת גרש ניסיתי לענות על צורכיהם של משתמשים מסוגים שונים: המשתמש האקראי הנזקק להקליד דואר בעברית, מתכנת הרוצה להוסיף פה ושם טקסט עברי לקוד מקור, ובלשן שאינו רוצה לוותר על הניקוד העברי.
+
+גרש, למרות פשטותו, מתאים לעולם המודרני:
+
+• גרש מאחסן את התווים בזיכרונו בייצוג יוניקוד UCS-4.
+
+• גרש מנצל מסופי UTF-8, כמו xterm והקונסולה של לינוקס, להצגת מגוון רחב של תווים, כולל combining characters (תווי ניקוד) ותווים רחבים (אידאוגרפים אסיאניים).
+
+• גרש משתמש בספריית FriBiDi כדי להשיג תאימות מלאה לתקן ה־BiDi של יוניקוד, וגרש אף תומך ב־Arabic shaping.
+
+• גרש משתמש בפונקציות iconv, אם מימושן קיים במערכת, כדי לשמור ולטעון קבצים במגוון רחב של קידודים. גרש מסוגל לזהות אוטומטית קידודים מסוימים.
+
+• גרש הנו עורך טקסט רב לשוני, אך יש בו כמה תכונות לטובת משתמשי העברית והערבית, כגון: יכולת להציג את סימני הניקוד העברי/ערבי (והמקף העברי) בצורה אלטרנטיבית, עריכה נוחה של תווי הפורמט של BiDi (כגון RLM, LRM), אלגוריתם אלטרנטיבי לקביעת כיווניות הפסקאות ואמולציה של מקלדת עברית.
+
+• גרש יכול לתפקד כפילטר (filter) להמרת טקסט לוגי לויזואלי (בדומה ל־bidiv).
+
+• גרש תומך במאייתים (spellers) של שפות שונות, כולל עברית.
+
+• צביעה תחבירית של קובצי HTML
+
+• undo/redo מקש למקש
+
+• העתקה/גזירה/הדבקה
+
+• ‏pipes
+
+• תפריט
+
+• צבעים
+
+• ועוד
+
+עם זאת, גרש פועל גם בסביבות פחות "מודרניות". למשל, הוא אינו מחייב תמיכה ב־UTF-8 locale.
+
+התפריט וקיצורי המקשים
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+ניתן לתת לגרש פקודות גם באמצעות התפריט וגם באמצעות קיצורי מקשים. למשל, אם נרצה לפתוח קובץ, נוכל לבחור בפקודת Open מתפריט File, אך נוכל גם ללחוץ F3. לצד כל פקודה בתפריט רשום קיצור המקשים המתאים לה.
+
+אל התפריט ניתן לגשת באמצעות המקשים F9 או F10. גם ביטול התפריט נעשה באמצעות מקשים אלה (או בלחיצה כפולה על ESC).
+
+בהמשך מדריך זה יצוינו בד"כ רק קיצורי המקשים של הפקודות ולא המסלול שלהן בתפריט, כי עיקר המדריך נכתב כשלגרש עדיין לא היה תפריט.
+
+להלן פירוט הקיצורים השימושיים ביותר, מקובצים לפי קטגוריה. רוב הקיצורים הועתקו מעורכים נפוצים אחרים כדי להקל על זכירתם. אתה, כמובן, רשאי להתעלם מקיצורים אלה ולהשתמש אך ורק בתפריט.
+
+Files
+‾‾‾‾‾
+F3 Load file / New file
+M-r Insert file
+F2 Save file
+C-s Save file as
+C-w Write selection to file
+C-M-c Change directory
+
+Movement
+‾‾‾‾‾‾‾‾
+left Move depending on the paragraph direction
+right Move depending on the paragraph direction
+C-b One char backward
+C-f One char forward
+M-v Toggle between logical and visual cursor movement
+up, C-p Previous line
+down, C-n Next line
+HOME, C-a Beginning of line
+END, C-e End of line
+PgUp Previous page
+PgDn Next page
+M-< Beginning of buffer
+M-> End of buffer
+M-b Word backward
+M-f Word forward
+F7 Search forward
+F17 Search next
+C-g Go to line #
+C-o, M-o Jump to the last modification point
+M-c Show/Hide cursor position
+C-M-s Change scroll step
+
+Editing
+‾‾‾‾‾‾‾
+BACKSPACE Delete previous char
+DELETE Delete char
+M-h, F12 Toggle alternate (=Hebrew) keyboard
+M-q Toggle smart-typing mode
+M-d Delete word forward
+M-BACKSPACE Delete word backward
+C-k Cut [to] end of paragraph
+C-y Delete paragraph
+M-J Toggle auto-justify
+C-M-j Change justify column
+C-j Justify current or next paragraph
+M-i Toggle auto-indent
+C-M-t Change tab width
+C-u Undo
+C-r Redo
+C-@, C-SPC Mark block / Unmark block
+C-c Copy
+C-x Cut
+C-v Paste
+C-q Translate next char
+C-M-v Insert Unicode char
+M-R Toggle read-only status
+C-M-e Cycle through various end-of-line types
+
+Spelling
+‾‾‾‾‾‾‾‾
+F5 Check spelling of whole document
+F6 Check spelling of document, starting from word under cursor
+M-$ Check spelling of word under the cursor
+M-S Explicitly load/unload the speller
+
+Display‎
+‾‾‾‾‾‾‾
+M-t Toggle the algorithm used to determine paragraph direction
+M-1 ... M-5 ditto
+C-M-b Toggle the BiDi algorithm
+M-w Toggle wrap type
+M-F Toggle display of formatting marks
+M-n Toggle display of Hebrew points
+M-k Toggle maqaf display
+M-a Toggle Arabic shaping
+C-l Refresh screen and center line
+
+Miscellaneous
+‾‾‾‾‾‾‾‾‾‾‾‾‾
+M-x Quit editor
+F1 Help
+F4 Describe key
+C-M-u Show character code and UTF-8 sequence
+C-M-i Show character info from UnicodeData.txt
+
+Help Window
+‾‾‾‾‾‾‾‾‾‾‾
+F1, M-x, 'q' Exit help
+ENTER Follow link
+'t' Move to Table of Contents
+'l', M-b Move back
+
+Dialog Line
+‾‾‾‾‾‾‾‾‾‾‾
+TAB Complete file name
+M-TAB Complete previous
+up, C-p Previous history
+down, C-n Next history
+C-c, C-g Cancel operation
+ESC ESC Cancel operation
+
+Menu
+‾‾‾‾
+F9, F10 Call up (and cancel) the menu
+C-c, C-g Cancel the menu
+ESC ESC Cancel the menu
+
+(גרש, נכון להיום, אינו מאפשר למשתמש הקצה לשנות את הקיצורים הללו. אם ברצונך לשנותם, ערוך את קובץ המקור bindings.cc והרץ שוב make.)
+
+הערות:
+
+1. M-x פירושו לחיצה על ALT ועל x בו־זמנית. לחלופין, אפשר להקיש ESC ואח"כ x (טיפ: אם מנהל החלונות שלך (FVWM, KDE וכד') לוכד את M-TAB (צירוף המשמש בגרש להשלמת שם קובץ), הקש ESC ואח"כ TAB).
+
+2. M-f שונה מ־ M-F. הראשון הוא לחיצה על ALT ועל אות "f" קטנה, השני על ALT ועל אות "F" גדולה.
+
+3. C-M-x פירושו לחיצה על Control ועל ALT ועל x בו־זמנית. לחלופין, אפשר להקיש ESC ואח"כ C-x (בשיטה חלופית זו יש להשתמש כאשר מנהל החלונות לוכד את צירוף המקשים המבוקש).
+
+4. אם שפת המקלדת אינה אנגלית, קרוב לוודאי שגרש לא יגיב לצירופים ALT + אות אנגלית. זו אינה בעיה בגרש, אלא בצורה בה פועלת מקלדת המסוף: גרש לא יראה אות אנגלית, אלא אות בשפה בה נמצאת המקלדת. פתרון אחד הוא להשתמש במפת "המקלדת החלופית" של גרש במקום בשירותי XKB או במקום ב־loadkeys (ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת"); פתרון אחר הוא לשנות את הגדרות המקלדת.
+
+5. Fn, כאשר 23 > ‏n‏ > 10, וכאשר המקש אינו נמצא על המקלדת, פירושו לחיצה על F(n-10)‎ ועל CTRL (או על shift, תלוי במסוף) בו־זמנית.
+
+עזרה
+‾‾‾‾
+גרש בא עם שני מקורות עזרה: המדריך למשתמש, בו אתה קורא כרגע, וכלי "תאר מקש", F4, המאפשר לך להקיש צירוף מקשים ולקבל עליו שורת הסבר. למעשה, ניתן לומר שגם התפריט על הפקודות המאורגנות בו הוא מקור עזרה.
+
+קריאת המדריך למשתמש נעשית בהקשה על F1. הקשה נוספת תחזיר אותנו אל העורך. גרש זוכר את הנקודה בה הפסקנו לקרוא את המדריך וכשנחזור אליו נמוקם באותה נקודה. הדבר מאפשר לדלג בנוחות בין המדריך לבין העורך וכך להתנסות בעורך תוך כדי קריאה ולימוד במדריך. הקשת ENTER תקפיץ אותנו אל הסעיף עליו מצביע הקישור תחת הסמן, והקשת 'l' תקפיץ אותנו אל הנקודה הקודמת בה היינו.
+
+כלי "תאר מקש" שימושי בעיקר במקרים בהם איננו זוכרים אם מקש מסוים אכן עושה מה שנדמה לנו שהוא אמור לעשות.
+
+המדריך למשתמש מנוסח בלשון רבים ולעתים בלשון זכר יחיד, אך בשפה העברית לשון זכר היא גם לשון ניטרלית, ולכן איני חייב התנצלות לאיש: לא לאלה שאינם מכירים את שפתם, ובעיקר לא לאלה המנסים להפוך אותי (ואותך) לשותף בהפצת האג'נדות שלהם.
+
+קידודים
+‾‾‾‾‾‾‾
+גרש הוא תוכנה "מבונאמת" (internationalized), וככזה הוא יכול לפעול תחת הגדרות קידוד שונות. המושגים "קידוד" ו"בנאום" (encoding ו־internationalization) שכיחים בדיונים אודות תכנות מודרניות, ובמסמך זה לא אטול על עצמי את המלאכה המייגעת של הסברתם. המעוניין יפנה למקורות הרבים והטובים המצויים באינטרנט.
+
+גרש מאחסן את התווים בזיכרונו בייצוג יוניקוד, UCS-4.
+
+גרש מודע לקידודם של שני מרכיבים, הנפרדים לחלוטין זה מזה:
+
+• קידוד המסוף
+
+• קידוד קובצי הטקסט
+
+קידוד המסוף
+‾‾‾‾‾‾‾‾‾‾‾
+קידוד המסוף פירושו קידוד התווים הנשלחים לתצוגה אל המסך וקידוד התווים המתקבלים מהמקלדת. במהלך פעולתו ממיר גרש את התווים מקידוד UCS-4 אל קידוד המסוף כדי להציגם על המסך, ובעת קבלת תווים מהמקלדת גרש ממיר אותם מקידוד המסוף אל קידוד UCS-4.
+
+על קידוד המסוף יש להצהיר באמצעות משתנה הסביבה LC_CTYPE (או LC_ALL או LANG). לדוגמה, אם קידוד המסוף שלנו הוא UTF-8, משתנה הסביבה LC_CTYPE עשוי יהיה להכיל את המחרוזת:
+
+en_US.UTF-8
+
+ואם קידוד המסוף שלנו הוא, לדוגמה, ISO-8859-8, אותו משתנה סביבה עשוי יהיה להכיל את המחרוזת:
+
+he_IL.ISO-8859-8
+
+או רק:
+
+he_IL
+
+רצוי להקליד "locale charmap" ב־shell כדי להיווכח שהמערכת אכן זיהתה את הקידוד שציינו.
+
+לגרש שני מצבי פעולה עיקריים: מצב UTF-8, בו המסוף מסוגל להציג ולקלוט אלפי תווים ממגוון שפות, ומאידך מצב "8 ביט" (לאלה משתייכים הקידודים המוכרים ממשפחת ISO-8859, ואחרים), בו המסוף מסוגל להציג ולקלוט מספר מצומצם יחסית של תווים, לרוב רק כמה עשרות. מערכות מודרניות תומכות במסופי UTF-8 ורצוי לנצל אפשרות זו. דוגמאות לכך הן הקונסולה של לינוקס (מוגבלת לתצוגה של 512 תווים, כאשר משתמשים בכרטיס VGA)‏, Linux framebuffer ו־xterm.
+
+מסופי UTF-8
+‾‾‾‾‾‾‾‾‾‾‾
+קיימים שני סוגים של מסופי UTF-8: המשוכללים יותר, אלה היכולים להציג combining characters ו־wide characters, ואלה שלא. לסוג הראשון משתייכים מסופים הפועלים בסביבות גראפיות, כגון xterm (וכנראה גם גרסאות אחרונות של Linux framebuffer), ולסוג השני משתייכים מסופים המשתמשים במצב הטקסט של כרטיס המסך, כגון הקונסולה של לינוקס.
+
+combining characters (ולעתים מכנים אותם non-spacing marks) הם סימנים גראפיים המצטרפים לתו הקודם, ועמם נמנים, בין היתר, סימנים דיאקריטיים, סימנים מתמטיים וסימני הניקוד העברי (פתח, סגול, דגש וכו', ועל כך קרא עוד בסעיף "ניקוד עברי").
+
+wide-characters הם תווים שרוחבם שתי עמודות. לקבוצה זו משתייכים האידאוגרפים האסיאניים (סינית, יפנית, קוריאנית ועוד) כי קשה לדחוס בעמודה אחת (שרוחבה כעשרה פיקסלים) "ציורים" מורכבים.
+
+גרש, בברירת מחדל, מנחש את סוג המסוף על פי קיומו של משתנה הסביבה DISPLAY. אם המשתנה קיים, גרש מניח שזהו מסוף מהסוג הראשון (כי כנראה מדובר באמולציית מסוף תחת X); אם המשתנה אינו קיים, גרש מניח שזהו מסוף מהסוג השני, המוגבל יותר. אם גרש לא מנחש נכונה את סוג המסוף (לדוגמה אם הנך משתמש ב־Linux framebuffer), יהיה עליך לספר לגרש על סוג המסוף באמצעות האופציה ‎`--combining-term'‎. אך אם תספר לגרש שהמסוף מסוגל להציג כראוי combining characters בעוד שהמסוף אינו מסוגל לכך, התצוגה עלולה להתפקשש, משום שגרש יניח שתווים אלה רוחבם 0 בעוד שמבחינת המסוף אלה הם תווים רגילים התופסים עמודה אחת.
+
+כדי לגלות מה חושב גרש על המסוף שלך, הפעילו עם האופציה ‎`-V'‎.
+
+לתמיכה במסופי UTF-8 חובה להדר ולקשר את גרש עם ספריית ncursesw, ספרייה המממשת את תקן X/Open ל־curses עם תמיכה ב־wchar_t. אם הדבר לא נעשה, ואם גרש ימצא שקידוד המסוף הוא UTF-8, גרש ידפיס הודעת שגיאה ויסרב להמשיך.
+
+<http://www.gnu.org/software/ncurses/ncurses.html>
+
+פרטים נוספים במסמך INSTALL.
+
+מסופי 8 ביט
+‾‾‾‾‾‾‾‾‾‾‾
+אלה הם מסופים מהסוג הישן -- מסופים בהם כל תו מיוצג ב־byte אחד. גם במצב פעולה זה חייב גרש לדעת את קידוד המסוף ולכן גם כאן יש להשתמש במשתני הסביבה LC_CTYPE וכד'.
+
+קיימות מערכות ישנות שבהן מנגנון ה־localization של ספריית ה־C לא עובד כשורה, או שהוא כלל לא קיים. גרש נכתב כך שיוכל לרוץ גם על מערכות כאלה. באמצעות האופציה ‎`--iso88598-term'‎ (או, בשמהּ הקצר, ‎`-H'‎) ניתן לומר לגרש שקידוד המסוף הוא ISO-8859-8 (עברי) ולגרום לו להשתמש בקוד המרה פנימי במקום בספריית ה־C. במצב פעולה זה כל מה שדרוש הוא שהמסך יוכל להציג תווים עבריים.
+
+תווי יוניקוד שגרש לא יכול לייצגם בקידוד המסוף ייוצגו, בברירת מחדל, כסימני שאלה בצבע סגול. combining characters ייוצגו כתווי גרש, ותווים רחבים ייוצגו כאות 'A'. כל אלה בצבע שונה מהטקסט הרגיל.
+
+תווי מסגרת גראפיים
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+למרות שהדבר אינו ממש קשור לנושא הקידוד, אתייחס כעת לתווים שבהם משתמש גרש לציור מסגרות התפריטים ופס הגלילה. בברירת המחדל ישתמש גרש בתווים "גראפיים" אם משתנה הסביבה DISPLAY קיים. אם המשתנה אינו קיים, ישתמש גרש בתווי ASCII. ניתן להנחות את גרש לנהוג אחרת באמצעות האופציה ‎`--graphical-boxes'‎ או באמצעות הפקודה "Toggle use of graphical characters for frames" אשר בתפריט Display.
+
+קידוד הקבצים
+‾‾‾‾‾‾‾‾‾‾‾‾
+גם הקבצים המצויים בדיסק מאוחסנים בקידוד מסוים. גרש מאפשר לנו לציין את קידוד הקובץ שאנו טוענים מהדיסק (כדי שתוכל להתבצע המרה מקידוד זה אל הקידוד הפנימי של גרש, UCS-4), או לציין את הקידוד שבו ישמר החוצץ כקובץ בדיסק (כדי שתוכל להתבצע המרה מן הקידוד הפנימי של גרש אל הקידוד הרצוי של הקובץ).
+
+לעובדה שגרש ממיר את קידוד הקובץ אל הקידוד הפנימי, UCS-4, יש גם חסרון: אי אפשר לערוך בגרש קובץ המכיל מספר קידודים, כדוגמת קובץ mbox. במקרים כאלה יש להשתמש בעורכים אחרים, הפועלים בצורה שונה מגרש, כגון mined.
+
+<http://www.towo.net/mined/>
+
+יש להדגיש שאין כל קשר בין קידוד הקבצים לבין קידוד המסוף: ניתן לערוך קובץ המקודד ב־UTF-8 על מסוף ISO-8859-8 ולהפך. אמנם, על המסוף יוצגו סמלים חליפיים במקום תווים שאותם הוא אינו יכול להציג, אולם ערכי התווים עצמם נשמרים בחוצץ (buffer=) ואינם אובדים.
+
+משתני הסביבה LC_CTYPE ואחרים אינם רלוונטיים כלל וכלל לקידוד הקבצים. הן משפיעים אך ורק על קידוד המסוף.
+
+טעינת קובץ
+‾‾‾‾‾‾‾‾‾‾
+הסוגיה החשובה ביותר בטעינת קובץ היא סוגיית הקידוד -- זיהויו הנכון של קידוד הקובץ.
+
+כאשר גרש מתבקש לטעון קובץ הוא נוהג כדלקמן:
+
+1. אם המשתמש ציין מפורשות את קידוד הקובץ, גרש יטען את הקובץ לפי קידוד זה;
+
+2. אם המשתמש לא ציין מפורשות את קידוד הקובץ, גרש יקרא את 8192 הבתים הראשונים של הקובץ וינסה לזהות את הקידודים הבאים: UTF-8, ואם הקובץ פותח ב"חתימה" (Byte-order mark -- כך מקובל בקבצים הנוצרים במערכת MS-Windows) אז גם את UTF-16 ו־UTF-32. אם גרש מצליח לזהות את הקידוד, הוא יטען את הקובץ לפי קידוד זה;
+
+3. אם המשתמש לא ציין מפורשות את קידוד הקובץ, ואם גרש לא הצליח לזהות אוטומטית את קידוד הקובץ, גרש יטען את הקובץ לפי קידוד ברירת המחדל. את קידוד ברירת המחדל ניתן לקבוע באמצעות האופציה ‎`-f'‎ או באמצעות תת התפריט "Set the default encoding" אשר בתפריט "Characters" (אם הדבר לא נעשה, קידוד ברירת המחדל יהיה זה שנקבע ע"י הסקריפט configure בשעת התקנת גרש, לרוב CP1255). קידוד ברירת המחדל קובע גם את קידודם של קבצים חדשים.
+
+לאחר טעינת הקובץ יציג גרש את קידודו בשורת הסטאטוס, בפינה הימנית.
+
+המשתמש יכול להזין לחוצץ (buffer=) את כל תווי היוניקוד מבלי להתחשב במגבלות קידוד הקובץ. כלומר, אם נטען קובץ המאוחסן בדיסק בקידוד ISO-8859-8, ולאחר מכן נזין לחוצץ מספר תווים ביפנית, עדיין נראה "ISO-8859-8" בשורת הסטאטוס. זה משום שהתווים תמיד מאוחסנים בחוצץ בייצוג UCS-4 -- קידוד היכול לייצג את כל התווים. תפקיד שורת הסטאטוס הוא רק להזכיר למשתמש באיזה קידוד מאוחסן הקובץ בדיסק.
+
+כאמור, המשתמש יכול לציין מפורשות את קידוד הקובץ שהוא מבקש לטעון. שתי דרכים לעשות זאת:
+
+1. אם הקובץ לטעינה מצוין כארגומנט בשורת הפקודה (ה־command line), ניתן לציין את קידודו באמצעות האופציה ‎`-F'‎ (שים לב: אות F גדולה, לא קטנה). במה שונה האופציה ‎`-F'‎ מהאופציה ‎`-f'‎? הראשונה מספרת לגרש מהו קידוד הקובץ, ואילו השנייה מספרת לגרש מהו קידוד ברירת המחדל, שיכנס לפעולה רק אם גרש לא מצליח לזהות אוטומטית את קידוד הקובץ. למעשה, האופציה ‎`-F'‎ היא הרבה פחות שימושית מאשר ‎`-f'‎ ונוכל לשכוח מקיומה.
+
+2. אם טעינת הקובץ נעשית מתוך גרש, למשל תוך שימוש בפקודות F3 או M-r, נוכל לציין את הקידוד באותה שורה בה אנו מקלידים את שם הקובץ: נקליד רווח אחרי שם הקובץ, לאחר מכן מינוס (או פלוס) ומיד לאחר מכן נקליד את שם הקידוד. לדוגמה, אם נרצה לטעון קובץ המאוחסן בקידוד יווני, נקליד:
+
+~/documents/filename.txt -iso-8859-7
+
+אם רוב הקבצים שלנו הם בקידוד יווני, נגדיר את הקידוד היווני כקידוד ברירת המחדל כדי שלא ניאלץ לציין את הקידוד שוב ושוב בכל טעינת קובץ.
+
+אם טעינת קובץ נכשלת עקב שגיאת קידוד -- למשל כאשר ננסה לטעון קובץ המאוחסן בקידוד ISO-8859-8 כקובץ בקידוד UTF-8 -- תוקרן הודעת שגיאה בנוסח "המרת UTF-8 נכשלה במיקום X".
+
+שמירת קובץ
+‾‾‾‾‾‾‾‾‾‾
+בשמירת קובץ יש שתי סוגיות: סוגיית הקידוד וסוגיית נהלי השמירה.
+
+נדון תחילה בסוגיית הקידוד.
+
+כאשר גרש מתבקש לשמור קובץ, הוא ממיר את התווים מייצוגם הפנימי, UCS-4, לייצוגם החיצוני -- קידוד הקובץ (זה המוצג בשורת הסטאטוס). אם גרש אינו מצליח לבצע את ההמרה, הסמן יעמוד על התו שבו נכשלה ההמרה ותוקרן הודעת שגיאה.
+
+נחזור לדוגמה שהובאה קודם: המשתמש טוען קובץ בקידוד ISO-8859-8. לאחר מכן המשתמש מזין מספר תווים ביפנית ומבקש מגרש לשמור את הקובץ. גרש מנסה לשמור את הקובץ באותו קידוד שבו הוא נטען, אך הוא לא מצליח להמיר לקידוד ISO-8859-8 את התווים היפניים, משום שלא ניתן לייצגם בקידוד זה. גרש יקפיץ את הסמן אל התו היפני הראשון ויקרין הודעת שגיאה האומרת ש"המרת ISO-8859-8 נכשלה במיקום X בתו Y".
+
+במקרה של השפה העברית נוכל לתאר מקרה פחות אזוטרי: המשתמש טוען קובץ בקידוד ISO-8859-8 ולאחר מכן מזין תו "מקף" או סימני ניקוד עברי. מכיוון שלא ניתן לייצג את התווים הללו בקידוד ISO-8859-8, ההמרה תיכשל.
+
+(למעשה, כאשר גרש מהודר/מקושר עם ספריית ICONV, מצב כזה לא יקרה, כי הקובץ יטען בקידוד CP1255, לא ISO-8859-8, ובקידוד זה כן ניתן לייצג את התווים המיוחדים הללו, אבל הדוגמה הנ"ל הובאה רק כדי להסביר את הרעיון הכללי.)
+
+כדי לפתור את הבעיה הזו מאפשר גרש למשתמש לציין מפורשות גם את הקידוד שבו ישמר הקובץ. אם המשתמש לא יציין את קידוד השמירה, גרש ינסה לשמור את הקובץ בקידוד שבו הוא נטען.
+
+ציון הקידוד בשעת השמירה נעשה בצורה דומה לציונו בשעת הטעינה: נשתמש בפקודה C-s, "שמור בשם", ולאחר שם הקובץ נקליד רווח, סימן מינוס (או פלוס) ושם הקידוד. שיטה אחרת לציון הקידוד היא באמצעות תת התפריט "Set the encoding used for saving this file" אשר בתפריט "Characters". מכאן ואילך יזכור גרש את הקידוד החדש ושורת הסטטוס תתעדכן בהתאם.
+
+אודות ICONV
+‾‾‾‾‾‾‾‾‾‾‾
+בסעיפים הקודמים דנּו בקידוד קבצים ואף נתנו דוגמאות לשמות קידודים, אבל עדיין לא הסברנו איזה מרכיב במערכת אחראי על ביצוע ההמרות.
+
+בשעת התקנת גרש יבדוק סקריפט ה־configure אם מותקנת במערכת ספריית ICONV. אם קיימת (כך כמעט בכל המערכות המודרניות), גרש ישתמש בה ולרשות המשתמש יעמדו שפע הקידודים שהספרייה מציעה. אם במערכת לא מותקנת ספריית ICONV, או אם ספרייה זו לא מתאימה לגרש, מסיבות שונות, יוכל המשתמש לשמור ולטעון קבצים רק ב־3 הקידודים הכלולים בגרש עצמו:
+
+• UTF-8
+
+• ISO-8859-8
+
+• ISO-8859-1 / ASCII
+
+כאשר המשתמש נצרך להקליד שם קידוד, בד"כ עליו להקליד את שמו הרשמי, אולם לעתים ספריות ICONV מודעות לווריאציות ול־aliases שונים, ולכן ייתכן שנוכל להקליד "utf8" במקום "utf-8",‏ "iso88598" במקום "iso-8859-8",‏ "windows-1255" שקול ל־"cp1255" וכו'.
+
+ניתן להקליד "iconv -l" ב־shell כדי לקבל את רשימת כל הקידודים המוכרים.
+
+מסקנות לגבי שמירה וטעינה של קבצים
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+בסעיפים הקודמים דובר על בעיות המרה בשמירה ובטעינה של קבצים. הבעיות הללו לא קיימות בקבצים המקודדים ב־UTF-8, כי בקידוד זה אפשר לייצג את כל התווים, ולכן טוב נעשה אם נרגיל את עצמנו להשתמש בקידוד זה.
+
+בסעיף "טעינת קובץ" תואר בפירוט תהליך זיהויו של קידוד הקובץ. תהליך זה, הכולל ניסיון זיהוי אוטומטי ולאחר מכן שימוש בקידוד ברירת המחדל, מתאים במיוחד למקרים בהם עובדים גם עם UTF-8 וגם עם קידוד מיושן, למשל כזה ממשפחת ISO-8859. במקרה כזה יש לקבוע את הקידוד המיושן כקידוד ברירת המחדל: אם הקובץ הוא דווקא בקידוד UTF-8, גרש יזהה זאת אוטומטית. מסיבה זו, בשעת התקנת גרש, קובע סקריפט ה־configure את קידוד ברירת המחדל כ־CP1255 (או כ־ISO-8859-8) ולא כ־UTF-8.
+
+שמות קבצים המכילים תווים שאינם ASCII
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+עפ"י המלצות של קהילות היוניקס, יש לקודד שמות קבצים ב־UTF-8, ואכן גרש מניח ששמות הקבצים מקודדים כך. אם גרש פוגש בשם קובץ שאינו רצף UTF-8 חוקי, גרש מניח שנעשה שימוש בקידוד ISO-8859-1.
+
+גרש מאפשר למשתמש להזין שמות קבצים בשני מקומות:
+
+1. הזנת שמות קבצים כארגומנטים בשורת הפקודה (command line): גרש טוען את הקובץ ששמו הוא רצף הבתים (bytes) המרכיב את הארגומנט.
+
+2. הזנת שמות קבצים בשורת הדיאלוג (בתגובה, למשל, להקשה על F3): שורת הדיאלוג היא כמו תיבת העריכה הראשית של גרש, אך בקטן. היא מאחסנת את התווים שהמשתמש מקליד בייצוג יוניקוד. כשגרש ניגש לטעון (או לשמור) את הקובץ, הוא מתרגם את השם שהקליד המשתמש ל־UTF-8.
+
+כלומר, אם ברשות המשתמש קובץ ששמו אינו מקודד ב־UTF-8, הוא יוכל לטעון אותו רק בציון שמו בשורת הפקודה. אם ינסה המשתמש להקליד את שמו בשורת הדיאלוג, גרש יטען קובץ בעל שם כזה אך המקודד ב־UTF-8. מכיוון שקובץ כזה לא נמצא בדיסק, גרש יפתח לעריכה קובץ חדש.
+
+נוהל שמירת קובץ
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+להלן הפעולות שגרש עושה בעת שמירת קובץ בשם filename:
+
+1. גרש שומר את החוצץ בקובץ זמני בשם filename.tmp. לקובץ הזמני אותן הרשאות כמו של filename, אם זה קיים. אם הפעולה נכשלת, גרש מקרין הודעת שגיאה ועוצר.
+
+2. אם המשתמש מעוניין בגיבוי, גרש ישנה את שמו של filename לשם קובץ הגיבוי. אם המשתמש אינו מעוניין בגיבוי, גרש ימחק את filename.
+
+3. גרש ישנה את שמו של הקובץ הזמני ל־filename.
+
+גיבוי
+‾‾‾‾‾
+בברירת מחדל, גרש מבצע גיבוי לקבצים. שם קובץ הגיבוי הוא שמו של הקובץ המקורי בתוספת סיומת. סיומת ברירת המחדל היא "~", אך אפשר לשנותה באמצעות האופציה ‎`--suffix'‎ או באמצעות משתנה הסביבה SIMPLE_BACKUP_SUFFIX (כך נהוג ב־fileutils של GNU). אם איננו מעוניינים בגיבוי, נציין מחרוזת ריקה כסיומת.
+
+שמירת חירום בעת קבלת אירוע SIGHUP
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כאשר גרש מקבל אירוע SIGHUP -- בד"כ תוצאה של ניתוק הקשר בין המסוף לבין השרת (למשל התנתקות חיבור האינטרנט בעת שימוש ב־telnet/ssh) או של סגירת חלון ה־xterm -- ואם השינויים שנעשו בחוצץ עדיין לא נשמרו בקובץ, גרש ישמור את החוצץ בקובץ בשם filename.save, כאשר filename הוא שם הקובץ שאנו עורכים. אם עדיין לא שייכנו שם קובץ לחוצץ, החוצץ ישמר בקובץ בשם geresh.save.
+
+עוד על תווים
+‾‾‾‾‾‾‾‾‾‾‾‾
+
+הזנת תווים שאינם נמצאים על המקלדת
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כאשר אנו מקלידים תווי אנגלית, הכל ברור ופשוט, כי הרי תווים אלה נמצאים על המקלדת, אך מה נעשה אם נצטרך להקליד תווים שאינם נמצאים על המקלדת, למשל תווים בעברית, ניקוד עברי, יפנית או רוסית?
+
+חמש דרכים שונות להזין תווים שאינם נמצאים על המקלדת:
+
+1. קנפג את המסוף שלך. למשל, אם אתה משתמש בקונסולה של לינוקס, עשה זאת באמצעות loadkeys; אם אתה משתמש ב־X11, עשה זאת באמצעות שירותי XKB.
+
+2. השתמש במפת "המקלדת החלופית" של גרש. הפעלה וביטול של מפה זו נעשים בהקשת M-h. המפה המגיעה עם גרש מדמה מקלדת עברית ("קראטון").
+
+3. תת־תפריט "Insert a Unicode character from the list" אשר בתפריט "Characters" מכיל מספר מצומצם של תווים שימושיים, והוא עשוי להיות שימושי עבור משתמשים חדשים שאין ברשותם הזמן ללמוד את גרש.
+
+4. השתמש במפת "תרגום התווים" של גרש. הפעלה של מפה זו נעשית בהקשת C-q והיא תקפה רק להמרת התו שיוקש אח"כ. מפה זו שימושית להזנת תווים שאיננו מקלידים לעתים קרובות (למשל ניקוד עברי), משום שלא נוח להקיש C-q לפני כל תו. המפה המגיעה עם גרש כוללת את סימני הניקוד העברי, תווי פורמט של BiDi ועוד כמה תווים שימושיים.
+
+5. השתמש בפקודת C-M-v, "הכנס תו", כדי להזין תו שערך היוניקוד שלו ידוע לך. לדוגמה, 2264 הוא הסימן "קטן שווה ל" (≤). פקודה זו נגישה גם מתפריט "Characters".
+
+מפות תווים
+‾‾‾‾‾‾‾‾‾‾
+"מפת תווים" של גרש היא טבלה המכילה התאמות בין תו אחד לאחר (בשפה מתמטית נאמר, כי מפת תווים הנה פונקציה). מפות תווים משמשות בד"כ להמרת תווים. גרש עושה שימוש ב־3 מפות תווים וכל אחת מהן מאוחסנת בקובץ משלה במערכת הקבצים:
+
+1. kbdtab - מפת "המקלדת החלופית" (ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת").
+
+2. transtab - מפת "תרגום התווים" (ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת") - trans = translation.
+
+3. reprtab - מפת ה"ייצוג" (repr = representation). מלבד טקסט, גרש נדרש להציג קונספציות ומצבים שונים: סופי פסקאות, אינדיקטורים לגלילת שורות או לקטיעת שורות, תווי ייצוג לתווים אסיאניים, לסימני ניקוד עברי, או ל־combining characters למסופים שאינם תומכים בהם, תווי פורמט של BiDi וכו'. במקום לקודד את תווי הייצוג הללו בגרש עצמו (hard-coded), גרש משתמש במפת תווים רגילה וכך מאפשר למשתמש לערוך אותה ולהתאים את תווי הייצוג לטעמו.
+
+שלושת הקבצים הללו עשויים להימצא בשני מקומות:
+
+1. ב־‎/usr/share/geresh/‎; או
+
+2. ב־‎~/.geresh/‎
+
+המיקום המדויק תלוי במערכת. הפעל את גרש עם האופציה ‎`-V'‎ כדי לקבל את רשימת הקבצים שגרש מחפש.
+
+התחביר של קובצי מפות התווים פשוט למדי: כל שורה כוללת שני תווים: תו המקור ותו היעד. תו ניתן לייצג בשלושה אופנים: ערך הקסדצימלי של התו (יוניקוד), ערך דצימלי (מספר המסתיים בנקודה) של התו (יוניקוד), או ליטרל התחום בגרש משני צידיו. הערות יש להקדים בתו "#". הקובץ מקודד ב־UTF-8.
+
+מומלץ לקרוא את תוכן קובצי המפות המסופקים עם גרש כי חלקם מכילים הסברים וטיפים.
+
+מידע על תווים
+‾‾‾‾‾‾‾‾‾‾‾‾‾
+תפריט "Characters" מכיל מספר פקודות המאפשרות לנו לקבל מידע על תווים.
+
+1. פקודת "Print Unicode and UTF-8 value" (או הקשה על C-M-u) תגלה לנו את ערך היוניקוד ואת רצף ה־UTF-8 של התו עליו עומד הסמן.
+
+2. פקודת "Insert a Unicode character using its code" (או הקשה על C-M-v) תאפשר לנו להזין תו שאת ערך היוניקוד שלו אנו יודעים.
+
+3. פקודת "Print the corresponding UnicodeData.txt line" (או הקשה על C-M-i) תדפיס את שורת ה־UnicodeData.txt המתאימה לתו עליו אנו עומדים. זו פקודה מאוד שימושית כאשר אנו צופים בקובץ המכיל תווים אזוטריים שאיננו יודעים מה פשרם, או כאשר אנו עובדים עם תווי ניקוד במסופים שאינם מסוגלים להציגם. גרש אינו מסופק עם הקובץ הנ"ל (בשל גודלו), אך אפשר להורידו מאתר יוניקוד:
+
+<http://www.unicode.org/Public/UNIDATA/UnicodeData.txt>
+
+הפעל את גרש עם האופציה ‎`-V'‎ כדי לראות באיזו ספרייה מצפה גרש למצוא את הקובץ.
+
+הסבר על מבנה הקובץ ניתן למצוא ב:
+
+<http://www.unicode.org/Public/UNIDATA/UCD.html>
+
+סופי שורות
+‾‾‾‾‾‾‾‾‾‾
+(הערה: בסעיף זה בכל מקום שכתוב "שורה" מדובר למעשה ב"פסקה" מבחינתו של גרש. "שורה" הוא המונח הרוֹוח בדיונים על "סופי שורות", ולכן בכתיבת הסעיף השתמשתי דווקא בו.)
+
+במערכות שונות מיוצגים סופי שורות בצורות שונות. גרש מסוגל לערוך מסמכים בהם סוגים שונים של סופ"ש.
+
+ +-----------------------+-------+---------------+
+ | סוג סופ"ש | מבנה | תו ייצוג בגרש |
+ +-----------------------+-------+---------------+
+ | יוניקס | LF |‏ « או » |
+ | דוס / וינדוז | CR+LF |‏ µ |
+ | מקינטוש | CR |‏ @ |
+ | יוניקוד: מפריד־פסקאות | PS |‏ ¶ |
+ | יוניקוד: מפריד־שורות | LS |‏ \ |
+ +-----------------------+-------+---------------+
+\html_repr <img src="imgs/eol.png">
+
+סופי השורות מוצגים רק כאשר מצב "הצג סימני פורמט" מופעל (M-F). את תווי הייצוג אפשר לשנות בעריכה של מפת ה"ייצוג" (ראה סעיף "מפות תווים").
+
+כאשר נקיש ENTER יצור גרש פסקה חדשה שסוף השורה שלה כסוף השורה של הפסקה המקורית. כדאי להיות מודעים לכך כאשר עורכים מסמך שבו סוגים שונים של סופי שורות.
+
+ניתן להחליף בין סוגי השורות באמצעות תת התפריט "Change end-of-line type" אשר בתפריט "Characters", או בהקשה על C-M-e. יש להקיש פעמים אחדות כדי להגיע לסוג הרצוי.
+
+למידע על הזנת מפריד־השורות של יוניקוד, ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת".
+
+המסך
+‾‾‾‾
+כאשר גרש רץ, מחולק המסך לארבעה אזורים. האזור העליון הוא השורה הראשונה במסך, הלא היא שורת התפריט. מתחתיה מתחיל אזור התופס את רובו המוחלט של המסך, והוא תיבת העריכה של גרש. בתחתית המסך שני אזורים נוספים: שורת הסטאטוס ומתחתיה שורת הדיאלוג.
+
+שורת הסטאטוס
+‾‾‾‾‾‾‾‾‾‾‾‾
+שורת הסטאטוס מציגה עבורנו את שם הקובץ שאותו אנו עורכים, את קידודו (בצד ימין), אינדיקטורים שונים (בצד שמאל), ואם ביקשנו, גם את מיקום הסמן.
+
+האינדיקטורים מספרים לנו על מצבי תיבת העריכה. לתיבת העריכה יש יכולות שונות שאפשר להפעיל (enable) או לבטל (disable). לדוגמה, מצב "הזחה אוטומטית" (auto-indent) יכול להיות מופעל או מבוטל. כאשר היכולת או המצב מבוטלים, האינדיקטור בד"כ יציג "-" (מינוס); כאשר היכולת או המצב פעילים, האינדיקטור יציג אות לטינית או סימן אחר כלשהו.
+
+להלן פירוט האינדיקטורים. שם לב שגם התפריט כולל אינדיקטורים (בדמות תווי '+' או '*'), ולכן אין צורך לשנן את האותיות הבאות; עם זאת, יש תועלת בשינון מקצתן (כמו למשל 'M').
+
+‏ ‎[M $/\ !/</> @ R S " a H j i k/K q n/N v F]‎
+ | | | | | | | | | | | | | | | |
+ | | | | | | | | | | | | | | | +-- האם החוצץ שונה מאז הטעינה?
+ | | | | | | | | | | | | | | | 'M' - כן.
+ | | | | | | | | | | | | | | | '-' - לא.
+ | | | | | | | | | | | | | | +-- סוג ה-wrap‏ (M-w):
+ | | | | | | | | | | | | | | '$' - אל תבצע wrap.
+ | | | | | | | | | | | | | | '-' - בצע, אבל אל תשבור מלים.
+ | | | | | | | | | | | | | | '\' - בצע, שבור מלים.
+ | | | | | | | | | | | | | +-- האלגוריתם הפעיל לקביעת
+ | | | | | | | | | | | | | הכיווניות (M-t):
+ | | | | | | | | | | | | | '!' - נהג עפ"י האלגוריתם המתואר
+ | | | | | | | | | | | | | ב־TR9.
+ | | | | | | | | | | | | | '-' - התחשב בסביבת הפסקה (פירוט
+ | | | | | | | | | | | | | בהמשך).
+ | | | | | | | | | | | | | '~' - התחשב בסביבת הפסקה (פירוט
+ | | | | | | | | | | | | | בהמשך).
+ | | | | | | | | | | | | | '<' - ישר את כל הפסקאות לשמאל.
+ | | | | | | | | | | | | | '>' - ישר את כל הפסקאות לימין.
+ | | | | | | | | | | | | +-- מצב סימון טקסט (C-@‎)
+ | | | | | | | | | | | +-- מצב "קריאה בלבד" (M-R)
+ | | | | | | | | | | +-- האם ה־Speller טעון? (M-S)
+ | | | | | | | | | +-- תרגם את התו הבא (C-q)
+ | | | | | | | | +-- מצב Arabic shaping‏ (M-a)
+ | | | | | | | +-- מצב מקלדת חלופית (M-h)
+ | | | | | | +-- מצב יישור אוטומטי (M-J)
+ | | | | | +-- מצב הזחה אוטומטית (M-i)
+ | | | | +-- הצג מקף עברי (M-k):
+ | | | | 'k' - יצגו כמינוס.
+ | | | | 'K' - יצגו כמינוס והציגו בהבלטה.
+ | | | | '-' - הדפיסו כפי שהוא, אם המסוף מסוגל לכך.
+ | | | +-- מצב "הקלדה חכמה" (M-q):
+ | | | 'q' - בשעת ההקלדה, המר "-" (מינוס) במקף עברי,
+ | | | אם בא לאחר אות עברית.
+ | | +-- הצג סימני ניקוד עברי וערבי (M-n):
+ | | 'n' - יצגם כתווי ASCII.
+ | | 'N' - הדפיסם כפי שהם, אם המסוף מסוגל לכך.
+ | | '-' - הסתר סימני ניקוד.
+ | +-- שיטת תנועת הסמן (M-v):
+ | 'v' - ויזואלית.
+ | '-' - לוגית.
+ +-- הצג סימני פורמט (M-F):
+ 'F' - הצג סופי פסקאות, תווי TAB ותווי פורמט של BiDi.
+ '-' - הסתר כל אלה.
+\html_repr <img src="imgs/indicators.png">
+
+שם לב: אם האינדיקטור הוא אות לטינית, בד"כ ניתן להפעיל ולבטל את המצב בהקשה על ALT ועל האות בו־זמנית.
+
+שורת הדיאלוג
+‾‾‾‾‾‾‾‾‾‾‾‾
+שורת הדיאלוג משמשת להצגת הודעות אינפורמטיביות, להצגת הודעות שגיאה, ולקבלת קלט מהמשתמש. קלט כזה עשוי להיות שמות קבצים, מחרוזות לחיפוש, או תשובות לשאלות כן/לא ("האם לשמור את השינויים?").
+
+בשעת הזנת קלט ניתן להשתמש כמעט בכל פקודות העריכה הזמינות בתיבת העריכה הראשית של גרש.
+
+כאשר מקלידים שם קובץ ניתן להשתמש במקש TAB (וב־M-TAB) כדי להשלים את שם הקובץ. השלמת שמות אפשרית גם עבור קבצים בעלי שמות בעברית ובכל שפה אחרת.
+
+לשורת הדיאלוג יש היסטוריה וניתן לנוע בה באמצעות המקשים חץ מעלה וחץ מטה.
+
+ניתן לבטל הזנת קלט, או אישור פעולה מסוימת, בהקשה על C-c, C-g, או פעמיים ESC.
+
+שפת הדיאלוג
+‾‾‾‾‾‾‾‾‾‾‾
+ניתן להדר ולקשר את גרש עם ספריית gettext, וכך, אם הקטלוגים הרצויים מותקנים במערכת, לבחור את שפת ההודעות שגרש מציג. הבחירה נעשית באמצעות משתני הסביבה LC_MESSAGES, LANG וכד'. גרש לא מסופק עם קטלוגים כאלה (כי אצטרך לתחזק אותם באופן שוטף), אבל האפשרות קיימת. אם ההודעות הן בעברית או בערבית, הן תיושרנה לימין.
+
+עריכת טקסט דו־כיווני
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+רוב הקשיים בעריכת טקסט בעברית אינם נובעים מכך שעברית נכתבת מימין לשמאל, אלא מכך שהטקסט בכללותו הוא דו־כיווני: עברית אמנם נכתבת מימין לשמאל, אבל בפסקה עברית סביר שנמצא פה ושם גם מספרים ומלים באנגלית, ואלה נכתבים משמאל לימין.
+
+בסעיף זה נדון באמצעים שונים שיקלו עלינו בעריכת טקסט דו־כיווני.
+
+תווי פורמט של BiDi
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+ב־TR9, המסמך המתאר את אלגוריתם ה־BiDi של יוניקוד, מפורטים מספר תווים המכונים "תווי פורמט". מטרתם להקל על המשתמש להשיג את התוצאה הויזואלית אליה הוא חותר. חלק מהתווים, כמו אלה המשנים את רמת ה־embedding, נושאים בחובם גם סמנטיקה.
+
+בהמשך סעיף זה אסביר רק על שני תווי פורמט: RLM ו־LRM, כי הם השימושיים ביותר. לפרטים על שאר תווי הפורמט, פנה ל־TR9.
+
+לעתים נוצרים סיבוכים בהקלדת טקסט מעורב. נבחן, לדוגמה, את שלושת המשפטים הבאים:
+
+• האיבר a[n] ערכו סכום האיברים a[0..n-1].
+
+• ביקרנו בתערוכת COMDEX. Microsoft לא הציגו שם.
+
+• RE (Regular Expressions) הם למחרוזות מה שהמתמטיקה היא למספרים.
+
+מה שקיבלנו על המסך הוא לא מה שציפינו לקבל.
+
+למרבה המזל עומדים לרשותנו שני תווים מיוחדים שיסייעו לנו לפתור את הבעיה; אלו הם תווים בלתי נראים אך בעלי כיווניות. אחד מהם הוא כעין תו עברי בלתי נראה (שמו בר"ת: RLM), השני -- תו אנגלי בלתי נראה (שמו בר"ת: LRM). נוכל לשלבם בטקסט כדי לקבוע את כיוונם של תווים ניטרליים או כדי לשבור רצף של תווים שאינם ניטרליים.
+
+נקליד שוב את המשפטים הנ"ל, אך הפעם נשתמש בתווים המיוחדים:
+
+• האיבר a[n]‎ ערכו סכום האיברים a[0..n-1]‎.
+
+• ביקרנו בתערוכת COMDEX. ‏Microsoft לא הציגו שם.
+
+• RE (Regular Expressions)‎ הם למחרוזות מה שהמתמטיקה היא למספרים.
+
+• RE ‏(Regular Expressions) הם למחרוזות מה שהמתמטיקה היא למספרים.
+
+ניתן לִצפות בתווים אלה כאשר מצב "הצג סימני פורמט" מופעל. הפעלה וביטול של מצב זה נעשים בהקשת M-F (תוכל לעשות זאת גם עכשיו, כאשר אתה קורא את המדריך למשתמש; שים לב: אות 'F' גדולה, לא קטנה), באמצעות האופציה ‎`--show-formatting'‎, או באמצעות תפריט "Display". איך התווים הללו, בין שאר תווי הפורמט, מוצגים על המסך -- זה תלוי במפת ה"ייצוג" (ראה סעיף "מפות תווים").
+
+מפת "תרגום התווים" המסופקת עם גרש (ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת" וסעיף "מפות תווים") מאפשרת לנו להזין את כל תווי הפורמט.
+
+ביטול זמני של אלגוריתם ה־BiDi
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+לעתים, כאשר אנו עורכים טקסטים מורכבים במיוחד (כמו HTML), ביטול זמני של אלגוריתם ה־BiDi יכול להקל על העריכה. הפעלה וביטול של אלגוריתם ה־BiDi נעשים בהקשת C-M-b (קיימת גם האופציה ‎`--bidi'‎). פסקאות עבריות עדיין תיושרנה לימין, ועדיין יבוצע mirroring לתווי סוגריים (הדבר נעשה כדי שהסוגריים המזוותים של HTML ייראו טבעיים ולא הפוכים; זה שיפור ניכר לעומת rightleft של Vim).
+
+תנועת סמן לוגית ותנועת סמן ויזואלית
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+שיטת הניווט הנפוצה ביותר בתוך המסמך היא, ללא ספק, באמצעות ארבעת מקשי החצים.
+
+בסעיף זה נעסוק בניווט בתוך שורת טקסט, כלומר, בתנועה אופקית, ולכן נדון כאן רק בשני מקשי חצים: חץ ימינה וחץ שמאלה.
+
+לכאורה, אין משהו מסובך במקשי החצים: אחד מצביע ימינה ואחד שמאלה, אבל מיד נראה שיש שתי דרכים שונות לפרש את תפקידם.
+
+נתבונן בשורת הטקסט הבאה ונדמיין שהסמן עומד על האות i אשר במלה Linux.
+
+ "יש הטוענים, כי מערכת ההפעלה Linux יציבה יותר מ..."
+
+מה צריך לקרות אם נקיש כעת על חץ שמאלה?
+
+יהיו שיטענו, בהגיון רב, שהסמן צריך לנוע אל האות L, כי היא נמצאת משמאל. מנגד, יהיו שיטענו, בהגיון רב גם כן, שכאשר המשתמש מקיש על חץ שמאלה בפסקה עברית הוא מבקש למעשה לנוע _קדימה_ בטקסט, לאו דווקא _שמאלה_, והאות n היא דווקא זו שנמצאת קדימה (ככל הכל, העובדה שהשפה האנגלית נכתבת משמאל לימין לא משנה את העובדה הזו, שקודם אנו מקלידים את האות i ורק אח"כ את האות n).
+
+שתי הדעות לגיטימיות, ובאמת, רוב מעבדי התמלילים מאפשרים למשתמש למַתֵג בין שתי השיטות.
+
+השיטה הראשונה, לפיה חץ שמאלה מניע את הסמן שמאלה על המסך, נקראת "תנועת סמן ויזואלית"; השיטה השנייה, לפיה חץ שמאלה מניע את הסמן קדימה, דהיינו, אל האות הבאה שהוקלדה, נקראת "תנועת סמן לוגית".
+
+גרש מאפשר למשתמש למתג בין שתי השיטות בהקשת M-v, באמצעות האופציה ‎`--visual-cursor'‎, או באמצעות תפריט "BiDi". כאשר תנועת הסמן היא ויזואלית, האינדיקטור יציג "v".
+
+איזו מהשיטות נוחה יותר? הגיונית יותר? זו שאלה סובייקטיבית, אבל רצוי להיות מודעים לכך שאפשר ליהנות משני העולמות: אפשר לעבוד עם שתי השיטות -- למתג ביניהן במהלך עריכת המסמך. בניגוד לעורכי טקסט אחרים (ליתר דיוק, מעבדי תמלילים אחרים), גרש אינו מחביא את האופציה הזו באיזו תיבת דיאלוג, אלא מציע למשתמש צירוף מקשים קל (M-v) למיתוג מיידי.
+
+כאן המקום לציין בעיה אחת שגרש סובל ממנה, ואשר אינה קיימת באותה עוצמה ביישומי GUI: הסמן של מסוף הטקסט, סמן הבלוק (block), מוגבל יותר מאשר הסמן הגראפי הדק, ה־caret, של יישומי ה־GUI, משום שכאשר הסמן עומד על הגבול בין רצף אנגלי לעברי לעתים קשה לדעת, מבחינה אינטואיטיבית, לאן הוא שייך/פונה. לכך יש השלכות לגבי עבודה בתנועת סמן ויזואלית: אנו עשויים להעמיד את הסמן על המקום שבו אנו סבורים שיוזן התו הבא, אך להפתעתנו, כאשר נקליד את התו, נמצא אותו במקום קצת אחר. הבעיה הזו פחות מטרידה כשאנו עובדים עם תנועת סמן לוגית (ומבינים אותה), וזו סיבה נוספת ליתרון בשימוש בשתי השיטות יחדיו (כלומר, נשתמש בתנועה ויזואלית כדי להגיע לאזור המעניין, ובתנועה לוגית כדי לבצע שינויים עדינים).
+
+אלגוריתם לקביעת הכיווניות
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+גרש אינו מעבד תמלילים, אלא עורך טקסט טהור, וטקסט טהור כשלעצמו אינו מכיל מידע על כיווניות הפסקאות. מכיוון שמידע כזה חסר, על גרש לנחש את הכיווניות של כל פסקה ופסקה כדי להציגן למשתמש בצורה מתאימה ונוחה.
+
+(כדי להקל על עצמי בכתיבת הסעיף, "עברי" להלן פירושו "בעל כיווניות מימין לשמאל" או "עברי או ערבי"; "אנגלי" פירושו "בעל כיווניות משמאל לימין".)
+
+גרש מציע לבחירת המשתמש חמישה אלגוריתמים לקביעת הכיווניות:
+
+1. אלגוריתם "יוניקוד" הנו חוקים P2 ו־P3 המתוארים ב"מסמך טכני מספר 9" של ארגון יוניקוד: כיוון הפסקה נקבע על פי התו החזק הראשון שבה. אם הפסקה ניטרלית -- כלומר, אם אין בה תו חזק -- הפסקה נחשבת אנגלית (האינדיקטור יציג "!").
+
+2. אלגוריתם "Contextual-strong" דומה לאלגוריתם יוניקוד לעיל, אבל נבדל ממנו בטיפול בפסקאות ניטרליות: כיוונה של פסקה ניטרלית נקבע עפ"י כיוון הפסקה הקודמת; אם כל הפסקאות הקודמות ניטרליות, כיוונה נקבע עפ"י כיוון הפסקה הבאה; לבסוף, אם כל הפסקאות בהודעה ניטרליות, הפסקה נחשבת אנגלית (האינדיקטור יציג "~").
+
+3. אלגוריתם "Contextual-rtl" דומה לאלגוריתם "Contextual-strong", אבל כיוון פסקה שאינה ניטרלית אינו נקבע על פי התו החזק הראשון שבה, אלא כדלקמן:
+.
+ א. אם קיים תו עברי בפסקה, הפסקה נחשבת כפסקה עברית (ולכן תיושר לימין);
+.
+ ב. אחרת: אם קיים תו אנגלי בפסקה, הפסקה נחשבת כאנגלית.
+.
+כלומר, ההבדל בין שני האלגוריתמים האחרונים הוא, שעבור האלגוריתם האחרון מספיק שבפסקה יהיה תו עברי אחד כדי שתיחשב כפסקה עברית. כך, אפילו הפסקה פותחת במלה אנגלית, היא עדיין תיחשב כעברית אם יש בהמשכה תווי עברית. (האינדיקטור יציג "-".)
+
+4. כל הפסקאות נחשבות כאנגליות (האינדיקטור יציג "<").
+
+5. כל הפסקאות נחשבות כעבריות (האינדיקטור יציג ">").
+
+שינוי האלגוריתם הפעיל נעשה בכמה דרכים:
+
+ א. בהקשת M-1 עד M-5.
+ ב. בהקשת M-t.
+ ג. באמצעות האופציה ‎`--dir-algo'‎.
+ ד. באמצעות תפריט "BiDi".
+
+אלגוריתם מס' 3 הוא אלגוריתם ברירת המחדל. אלגוריתם "יוניקוד", מס' 1, הוא אלגוריתם עם תוצאות גרועות במיוחד, אבל מפעם לפעם כדאי להפעילו כדי לראות איך הטקסט יוצג על גבי מערכות הפועלות לפיו (כגון יישומי KDE/Qt). אלגוריתם מס' 2 הוא הנהוג ב־Pango של GTK (כך דוּוח לי).
+
+ייתכן שבעתיד אוסיף אלגוריתם או שניים, לתאימות עם bidiv.
+
+טיפ למשתמשים באלגוריתמים "יוניקוד" או "Contextual-strong": ניתן לנצל את התו RLM כדי לישר לימין פסקה הפותחת במלה אנגלית. יש להקליד את התו הבלתי נראה הזה בתחילת הפסקה ממש.
+
+עברית
+‾‾‾‾‾
+
+ניקוד עברי
+‾‾‾‾‾‾‾‾‾‾
+לסימני הניקוד חלק חשוב בטקסט העברי, שהרי חובה עלינו להשתמש בהם כדי להבדיל בין מלים בעלות כתיב זהה (הומוגראפים): יש דְבֿוֹרִים ויש דַבּוּרִים).
+
+מכיוון שלמסופים שונים יכולות שונות, גרש מאפשר להציג את סימני הניקוד העברי ב־3 אופנים:
+
+1. לא להציגם כלל. הם קיימים בטקסט אבל לא נראה אותם על המסך.
+
+2. לייצגם בתווי ASCII ולהקרינם בצבע שונה. שיטה זו של הצגת סימני הניקוד עובדת בכל מסוף. למשל, הניקוד "קמץ" ייוצג באות "A", חולם באות "O" ודגש בתו "*" -- כולם בצבע ירוק כדי להבדילם מהטקסט (טעמי המקרא -- ולצורך כתיבת מסמך זה איני מבדיל בינם לבין סימני הניקוד -- מוצגים בצבע כחול). נוכל לשנות את תווי ה־ASCII המייצגים כל סימן באמצעות עריכה של מפת ה"ייצוג" (ראה סעיף "מפות תווים"). אם אינך זוכר איזה סימן מייצגת אות מסוימת, הקש C-M-i כדי לקרוא את תיאור התו (לפרטים נוספים, ראה סעיף "מידע על תווים").
+
+3. להציגם כפי שהם. שיטה זו עובדת, כמובן, רק במסופים המסוגלים להציג את סימני הניקוד העברי.
+
+שינוי אופן התצוגה נעשה בהקשת M-n, באמצעות האופציה ‎`--points'‎, או באמצעות תפריט "Display".
+
+כדי שמסוף יהיה מסוגל להציג את סימני הניקוד העברי (אופן 3 לעיל), שלושה תנאים חייבים להתקיים:
+
+1. על המסוף לתמוך ב־combining characters.
+
+2. יש להשתמש בגופן שבו מצויים גליפים של סימני הניקוד העברי.
+
+3. יש להשתמש בגופן שבו מצויים גליפים של Hebrew presentation forms; או, לחלופין, יש לבטל את פעולת הנִרמול של תוכנת המסוף.
+
+Hebrew presentation forms מכילים בעיקר גליפים של אותיות הא"ב עם דגש בתוכן. אילולא היו קיימים גליפים אלה, המסוף פשוט היה מדפיס את גליף הדגש על הגליפים של אותיות הבסיס והתוצאה הייתה מעוותת, כי הפיקסל של סימן הדגש עלול היה להתערב עם הפיקסלים של אות הבסיס (נתאר לעצמנו את האותיות צד"י ושי"ן, שאין במרכזן חלל).
+
+כאשר תוכנת המסוף, xterm לדוגמה, פוגשת ברצף תווים (תו בסיס + combining characters) שאפשר להביעו בתו (=גליף) אחד -- הנקרא precomposed-character -- היא משתמשת בתו האחד הזה. את פעולה זו אכנה להלן "נִרמול" (ע"ש "Normalization Form C"), והיא הסיבה לכך ששליחת האות צד"י ואחריה דגש יגרמו להדפסת גליף המצוי בבלוק ה־presentation forms.
+
+אם אין אנו משתמשים בגופן המכיל Hebrew presentation forms אזי נראה קוביות ריקות במקום רצפי תווים שעבורם קיימים precomposed-characters. במקרה כזה נותרה לנו תקווה אחת: אם נוכל לבטל את ביצוע הנרמול של תוכנת המסוף, היא לא תחפש precomposed-character, אלא תדפיס את גליף הסימן הדיאקריטי או את גליף סימן הניקוד על גבי אות הבסיס (והתוצאה הויזואלית עלולה, כאמור, להיות מעוותת במקצת). לתוכנת xterm אין מתג לביטול הנרמול, ולכן ניאלץ לערוך את קוד המקור (נמחק מהרשימה שב־precompose.c את כל התווים העבריים) -- אבל כל זאת, כמובן, רק אם אין ברשותנו גופן המכיל presentation forms.
+
+בדבר גופן המכיל presentation forms, ראה סעיף "המלצה: גופן".
+
+אם נזדקק להוסיף לאות בסיס מספר סימני ניקוד, נעשה זאת בסדר זה:
+
+1. דגש או רפה;
+
+2. הנקודה הדיאקריטית של שי"ן (זו המבחינה בין שי"ן ימנית לשי"ן שמאלית);
+
+3. סימן תנועה (ו/או סימנים אחרים).
+
+כך נשמור על תאימות עם MS-Windows ורק כך יוכל xterm (ותכנות אחרות) לבצע נרמול.
+
+למידע על הזנת תווי ניקוד, ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת".
+
+הערה: ב־ncursesw יש באג שאינו מאפשר ל־combining character לבוא בעמודה הימנית ביותר במסך. כלומר, סימן ניקוד באות הראשונה בשורה יתפקשש קמעה. אבדוק את הנושא.
+
+מקף
+‾‾‾
+את המקף העברי ניתן להזין בשבעה אופנים:
+
+1. בצורה ידנית: כפי שאנו מזינים כל תו אחר שאינו מצוי על המקלדת. ראה סעיף "הזנת תווים שאינם נמצאים על המקלדת" -- שם מפורטות חמש דרכים. בפרט, מפת "תרגום התווים" המגיעה עם גרש מאפשרת להזין מקף בהקשה על "-" (מינוס) לאחר C-q.
+
+6. בצורה ידנית: גרש מאפשר להזין מקף בהקשה על ALT ומינוס יחדיו. מנגנון זה טבוע בגרש עצמו ואינו קשור למפות התווים של גרש.
+
+7. בצורה אוטומטית: כאשר מצב "הקלדה חכמה" מופעל (בהקשת M-q, באמצעות האופציה ‎`--smart-typing'‎, או באמצעות תפריט "Characters"), גרש ימיר תו "מינוס" בתו מקף, בשעת ההקלדה, אם מופיע לפניו תו עברי (לכן, אם נרצה להפיק "קו מפריד", ולא מקף, נקפיד להקליד רווח לפני תו המינוס. הדבר עולה בקנה אחד עם המלצות האקדמיה ללשון העברית, לפיהן יש לשמור על רווח בין "קו מפריד" לבין המלים הסובבות).
+
+את המקף ניתן להציג על המסך בשלושה אופנים:
+
+1. כפי שהוא. זה, כמובן, יעבוד רק אם בגופן שבו אנו משתמשים מצוי גליף לתו המקף.
+
+2. לייצגו בתו ASCII "מינוס" (dash).
+
+3. לייצגו בתו ASCII "מינוס" (dash), ובנוסף, בצבע שונה מהטקסט הרגיל. "מבט־על" זה של כל המקפים יאפשר לנו להעריך (לחיוב או לשלילה) את השימוש שאנו עושים במקף. לדוגמה, זה יאפשר לנו להעריך עד כמה מהטקסט לא יהיה ברור למשתמשים שיצפו בו על גבי מערכות שאינן מסוגלות להציג מקף. זוהי ברירת המחדל.
+
+שינוי אופן התצוגה של המקף נעשה בהקשת M-k, באמצעות האופציה ‎`--maqaf'‎, או באמצעות תפריט "Display".
+
+התווים גרש, גרשיים ומרכאות
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+עורכי טקסט ומעבדי תמלילים מסוימים יודעים להמיר את תווי הגרש והמרכאות הפשוטים, המצויים על המקלדת, בתווים הנושאים משמעות רבה יותר (מרכאות פותחות, מרכאות סוגרות וכו'). אך מכיוון שאיני מתמצא בכללים הטיפוגרפיים הנהוגים בעברית, מצב "הקלדה חכמה" של העורך כלל אינו מעניק טיפול מיוחד לתווים אלה. כנ"ל לגבי הווריאציות הרבות של הסימן הנראה כקו אופקי ("-"). אולי בעתיד.
+
+ערבית
+‾‾‾‾‾
+הרבה ממה שנכתב לעיל בנושא העברית תקף גם לערבית. ההבדל המרכזי בין הכתב העברי לערבי הוא שהכתב הערבי מחובר.
+
+חיבור אותיות
+‾‾‾‾‾‾‾‾‾‾‾‾
+בשפה הערבית 28 אותיות (בשפות אחרות הנכתבות בכתב הערבי, כמו פרסית, קיימות אותיות נוספות), ולרובן 4 וריאציות (גליפים) בהתאם להקשרן במלה: תחילית, אמצעית, סופית־מחוברת ועצמאית. הטקסט הערבי מאוחסן בזיכרון המחשב בייצוג נומינלי: מאוחסנות האותיות עצמן, ולא הווריאציות השונות בהן הן מופיעות על המסך. זהו תפקידו של מנוע התצוגה לבחור ולהציג את הווריאציה המתאימה לאות בהקשרה. פעולה זו נקראת Arabic shaping, וגרש יודע לבצעה. כדי שתהיה בכך תועלת, עלינו להשתמש בגופן הכולל את הווריאציות הרבות של האותיות (אלו נקראות Arabic presentation forms), אחרת נראה קוביות או סימני שאלה במקום אותיות.
+
+הפעלה או ביטול של Arabic shaping נעשים באמצעות האופציה ‎`--arabic-shaping'‎, בהקשת M-a, או באמצעות תפריט "Display".
+
+בכתב הערבי מקובלות גם מספר ליגטורות. ליגטורת חובה היא הליגטורה לאם־אליף: הרצף לאם ואחריו אליף (ل,ا) מוצג כגליף אחד (لا). כאשר Arabic shaping מופעל, גרש מבצע גם עיבוד זה.
+
+ניקוד ערבי
+‾‾‾‾‾‾‾‾‾‾
+בדומה לעברית, גם בערבית יש סימני ניקוד (حركات).
+
+כל מה שנאמר לעיל בנוגע לניקוד עברי תקף גם לניקוד ערבי. ראה סעיף "ניקוד עברי".
+
+הדרישות להצגה של הניקוד הערבי: שימוש בגופן הכולל את סימני הניקוד ושימוש במסוף התומך ב־combining characters.
+
+כאשר גרש מייצג את הניקוד הערבי בתווי ASCII, הם מודפסים בצבע צהוב כדי להבדילם מתווי הייצוג לניקוד העברי (אשר מודפסים בצבע ירוק).
+
+המלצה: גופן
+‾‾‾‾‾‾‾‾‾‾‾
+אני ממליץ על הגופן Courier New של מייקרוסופט. כן, אני יודע שבמערכות ההפעלה שבהן גרש מופעל זה לא פופולרי לומר דברים בשבחה של מייקרוסופט, אבל חייבים לתת קרדיט איפה שמגיע. שלוש סיבות להמלצה שלי:
+
+1. זהו גופן True Type ולכן ניתן להשתמש בו בגדלים שונים.
+
+2. תווי העברית בפונטים המגיעים עם XFree86 אינם נעימים לעין במקרה הטוב, ומכוערים במקרה הפחות טוב.
+
+3. זהו פונט יוניקוד המכיל, בין היתר, תווי עברית (כולל ניקוד), ערבית (כולל ניקוד) ורוסית, וכן presentation forms של עברית וערבית.
+
+חסרונו של הפונט שהוא לא מכיל תווים איזוטריים (כמו תווי מסגרות, אידאוגרפים אסיאניים, אמהרית, סימנים מתימטיים, תאילנדית, ברייל, ועוד לא מעט דברים שיכלו לשמח יודעי דבר).
+
+המלצה: גופן נוסף
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+המלצה נוספת שלי היא חבילת גופני היוניקוד המתוחזקים ע"י Markus Kuhn:
+
+<http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html>
+
+(בעבר העדפתי את הפונטים הללו על פני הפונט Courier New של מייקרוסופט, אבל שיניתי את דעתי. העברית בפונטים של Markus לא ממש נעימה לעין.)
+
+גופנים אלה גם מסופקים יחד עם XFree86 ושרתי X אחרים, אבל ה־presentation forms של עברית וערבית הם תוספת מאוחרת ויתכן שהם אינם מצויים ברשותך, לכן הקפד להוריד ולהתקין את הגרסה האחרונה של הגופנים.
+
+הערה: קובץ ה־Makefile של גופנים אלה כולל פקודה למחיקת כל התווים מעבר ל־U+3200 (כדי לחסוך זיכרון, וההסבר המלא בקובץ ה־README), וביניהם ה־presentation forms (זו סיבה נוספת לאפשרות שהפונט המגיע יחד עם ה־X11 שלך לא יגרום לך נחת רוח). לכן, לפני שאתה מבצע "make", ערוך את ה־Makefile והחלף את כל המופעים של "3200" ב־"FFFF".
+
+כדי לבדוק אם הגופנים שברשותך כוללים presentation forms, בצע:
+
+$ xfd -start 64256 -fn <font-name>
+(to check for accented hebrew letters)
+/צילום_מסך imgs/xfd_heb.png, שים לב לאותיות העבריות שבתמונה: בתוכן דגש.
+
+$ xfd -start 65024 -fn <font-name>
+(to check for arabic contextual glyphs)
+/צילום_מסך imgs/xfd_ara.png, שים לב לאותיות הערביות שבתמונה: לכל אות מספר וריאציות וכמו כן מצויה כאן ליגטורת לאם־אליף.
+
+המלצה: תוכנת אמולצית מסוף
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+אני ממליץ על xterm. זוהי תוכנה טובה התומכת ב־UTF-8:
+
+<http://dickey.his.com/xterm/xterm.html>
+
+xterm, החל מהגרסה המסופקת עם XFree86 4.0, תומך ב־UTF-8. הגרסה המסופקת עם XFree86 4.0.3 ואילך תומכת גם ב־combining-characters. הקלד "xterm -version" כדי לקבל את מספר הגרסה.
+
+ל־xterm יש patch לתמיכה ניסיונית ב־BiDi, אך אין להשתמש בו עם גרש, משום שגרש מניח שהמסוף מציג עברית ויזואלית. על הקונספציה לפיה מסופים צריכים לתמוך ב־BiDi מוטל סימן שאלה גדול.
+
+אם תמיכה ב־UTF-8 (מצד המסוף) אינה חשובה לך, תוכל להשתמש בכל תוכנת אמולצית מסוף אחרת (עדיין תוכל לערוך קבצים המקודדים ב־UTF-8, כי אין קשר בין קידוד המסוף לקידוד הקבצים, והדבר מוסבר בהרחבה בסעיף "קידודים").
+
+מספר מלים על konsole
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+בפעם האחרונה שבדקתי את konsole (גרסה 1.1) של KDE (גרסה 3.0.0-10) נתקלתי בבעיות הבאות:
+
+• טקסט עברי/ערבי עובר היפוך, כי konsole אינו מנטרל את אלגוריתם ה־BiDi של Qt. בנוסף, konsole אינו מודע לכך שהטקסט, עקב ההיפוך, מופיע במקומות שבהם הוא אינו אמור להופיע, ולכן החלון מתלכלך במהירות בתווים במקומות לא צפויים ואי אפשר לנקותו
+.
+_עדכון_: מני ליבנה, מצוות KDE, מוסר לי כי הבעיה תוקנה ב־KDE 3.1.
+
+• נראה ש־konsole אינו תומך ב־combining characters (אלו הם, בין היתר, סימני הניקוד העברי).
+
+• התצוגה של konsole אטית במקצת.
+
+לכן איני ממליץ על konsole. יתכן שגרסאות עדכניות של konsole תומכות ב־combining characters, אבל איני יודע. אין לי גישה למערכת KDE עדכנית ולכן איני יכול לבדוק זאת. העובדה שאיני ממליץ על השימוש בתוכנה מסוימת אין פירושה שאני ממליץ לא להשתמש בה, אלא פשוט שאיני יכול להמליץ על תוכנה שאיני מכיר.
+
+חרטה (undo)
+‾‾‾‾‾‾‾‾‾‾‾
+גרש תומך בשתי שיטות ניהול שונות של undo:
+
+1. קיבוץ פעולות קטנות. אם נקיש מספר פעמים על backspace כדי למחוק משפט, גרש יזכור שמחקנו קטע של טקסט, וכשנבצע undo, גרש יקים לתחייה את המשפט כולו.
+
+2. שמירה על הפעולות הבדידות. אם נקיש מספר פעמים על backspace כדי למחוק משפט, גרש יזכור כל מחיקת תו כפעולה נפרדת, וכל פעם שנבצע undo, גרש יקים לתחייה תו אחד.
+
+בחירת השיטה נעשית באמצעות האופציה ‎`--key-for-key-undo'‎, או באמצעות הפקודה "Toggle key-for-key undo" אשר בתפריט "Edit".
+
+גרש שומר את כל הפעולות שביצענו באזור זיכרון מיוחד, שגודלו מוגבל. אם נבצע הרבה פעולות, גרש "ישכח" את הפעולות הראשונות שבצענו ולא נוכל לבטלן. ניתן לקבוע את כמות הזיכרון המוקצית ל־undo באמצעות האופציה ‎`--undo-size'‎. ברירת המחדל היא 50k.
+
+גלילה
+‾‾‾‾‾
+המקשים PgUp ו־PgDn גוללים את המסמך מעלה או מטה, כמקובל. עורכי טקסט שונים זה מזה בצורת הביצוע המדויקת של הפעולה; משתמשים רבים מתלוננים על כך שהם "מאבדים" את נקודת המבט שלהם כשהם גוללים טקסט, ולכן גרש מחקה את צורת הפעולה של emacs, מתוך הנחה שעורך הטקסט הנפוץ הזה הותאם במשך השנים לטעמם של רוב המשתמשים.
+
+כאשר יקיש המשתמש על מקש Down בשעה שהסמן נמצא בשורה האחרונה על המסך, יגלול גרש את המסמך שורה אחת מעלה, כברירת מחדל. באופן דומה יגיב גרש למקש Up. משתמשים רבים רגילים לכך כי כך מקובל ביישומי MS-Window. כדי להנחות את גרש לגלול את המסמך מספר אחר של שורות, כרצוננו, נשתמש באופציה ‎`--scroll-step'‎ (או, בתוך גרש, באמצעות תפריט "Display"). אם נרצה לחקות את צורת הפעולה של emacs, בה נגלל המסמך חצי מסך, נספק ארגומנט גדול, כמו 99.
+
+פקודה שימושית נוספת היא C-l. הפקודה תגרום לרענון המסך, כמקובל ביישומי יוניקס, אך בנוסף גם תמרכז את השורה הנוכחית.
+
+פס הגלילה
+‾‾‾‾‾‾‾‾‾
+כאשר אנו עורכים מסמך ארוך נוכל להיעזר בפס הגלילה כדי לדעת את מקומנו היחסי במסמך. פס הגלילה אינו מוצג בברירת המחדל ויש להזמינו באמצעות תת התפריט "Scrollbar" אשר בתפריט "Display" או באמצעות האופציה ‎`--scrollbar'‎.
+
+קיפול פסקאות (wrap)
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כיצד מציג גרש פסקאות הארוכות מרוחב המסך? גרש מציע לבחירת המשתמש שלוש אפשרויות (מלבד אפשרות "יישור אוטומטי", שתתואר בהמשך):
+
+1. המשך הפסקה יופיע "מעֵבר" לגבול המסך ויהיה צורך לגלול אופקית כדי לקרוא אותו (מקובל לכנות זאת wrap=off). כאשר הפסקה ממשיכה מעבר לגבול המסך, יופיע הסימן "$" בנקודת הקטיעה (אפשר לשנותו בעריכת מפת ה"ייצוג"). האינדיקטור יציג "$".
+
+2. הפסקה "תקופל" ותשתרע על פני מספר שורות־מסך. גרש לא ישבור מלים לצורך הקיפול. זו ברירת המחדל. האינדיקטור יציג "-".
+
+3. הפסקה "תקופל" ותשתרע על פני מספר שורות־מסך. גרש כן ישבור מלים לצורך הקיפול, ובמקום הקיפול, על גבול המסך, יופיע הסימן "/". האינדיקטור יציג "/".
+
+שינוי אופן התצוגה נעשה בהקשת M-w, באמצעות האופציה ‎`--wrap'‎, או באמצעות תפריט "Display".
+
+יישור אוטומטי
+‾‾‾‾‾‾‾‾‾‾‾‾‾
+דרך אחרת להתמודד עם שורות (=פסקאות) ארוכות היא "יישור אוטומטי". זהו התרגום העברי, וקרוב לוודאי השגוי, של המונח האנגלי justification/auto-justify, הגם־כן שגוי, אבל מכיוון ש־pico משתמש בו, גם אני משתמש בו. ב־emacs הדבר נקרא auto-fill.
+
+כאשר מצב auto-justify פעיל, אזי כאשר המשתמש מקליד טקסט שגורם לשורה (=לפסקה) להתארך מעבר לאורך מקסימלי מסוים, הקרוי "עמודת היישור" (justify column), השורה (=הפסקה) תתפצל לשתיים כדי לא לחרוג מאותו אורך.
+
+הפעלה וביטול של מצב auto-justify נעשים בהקשת M-J (שים לב: אות "J" גדולה, לא קטנה) או באמצעות האופציה ‎`--auto-justify'‎; שינוי עמודת היישור בהקשת C-M-j. כל פקודות אלה מצויות גם תחת תפריט "Edit".
+
+ניתן "ליישר" פסקה שלמה בהקשת C-j. הפקודה תעבוד גם כאשר מצב auto-justify לא פעיל. לאחר ביצוע היישור הסמן ידלג לסוף הפסקה וניתן יהיה להמשיך ולהקיש C-j כדי ליישר את שאר המסמך.
+
+חשוב להבין שהעובדה שגרש תומך ב"יישור" לא אומרת שאתה חייב להשתמש בזה. יש להשתמש ב"יישור" רק אם יש בכך צורך, ואם אתה לא רואה את הצורך, סימן שאתה לא צריך ליישר (או שאתה לא יודע מה אתה עושה :-). הקונספציה של שבירת שורות תיראה משונה למדי למשתמשי MS-Windows, אבל היא דבר מקובל בעולם של יוניקס.
+
+יש המקפידים להגביל את אורך השורות בהודעות דואר, אבל כדאי להיות מודעים לפורמט flowed, המתואר ב־RFC 2646. כדי להקל על עריכת הודעות דואר בפורמט זה, גרש שומר את הרווחים בסופי השורות כאשר הוא מיישר פסקאות. אם אינך מעוניין לשמור את הרווחים, השתמש באופציה ‎`--rfc2646-trailing-space'‎.
+
+בדיקת איות
+‾‾‾‾‾‾‾‾‾‾
+גרש יודע לבדוק איות באמצעות המאיית ispell, או באמצעות כל מאיית אחר המממש את פרוטוקול ispell-a. דוגמאות למאייתים כאלה הם aspell, המסופק עם הפצות רבות של לינוקס, והמאיית העברי hspell, לו מוקדש סעיף נפרד במסמך זה.
+
+גרש מהווה את הממשק הגרפי של המאיית: כאשר המאיית מוצא מלה שאיותהּ שגוי, גרש מבליט (highlight) את המלה על המסך ומציג למשתמש תפריט ובו הצעות לאיות חוקי של המלה, אם המאיית מוצא כאלו.
+
+אם מותקן במחשבך המאיית העברי hspell, גרש יזהה אותו ותהיה פטור ממלאכת הקינפוג. במקרה זה תוכל לדלג ישר לסעיף "עבודה עם המאיית".
+
+כדי שגרש יוכל לתקשר עם המאיית, על המשתמש לספק לו שני פרטי מידע:
+
+1. הפקודה המשמשת להרצת המאיית, לדוגמה ‎"/usr/bin/ispell -a"‎.
+
+2. הקידוד שבו יתקשר גרש עם המאיית, לדוגמה "ISO-8859-1".
+
+נושא ה"קידוד", בהקשר של בדיקת איות, ייתכן שיהיה חדש לרוב המשתמשים, כי רובנו (משתמשי לינוקס/יוניקס) רגילים לבדיקת איות באנגלית בלבד -- שפה שממילא מיוצגת בכל הקידודים הנפוצים בצורה זהה, ולכן נדון בו תחילה.
+
+עורכי טקסט ישנים היו מאחסנים בזיכרונם כל תו ב־byte אחד, והיו שולחים את הטקסט למאיית כמות שהוא. כיום, עורכי הטקסט המודרניים תומכים ב־Unicode, ואילו רוב המאייתים ממשיכים "לדבר" בשפה המצומצמת של byte אחד עבור כל תו. מסיבה זו עורך טקסט מודרני מאפשר למשתמש לציין את הקידוד שבו תתבצע התקשורת עם המאיית. לשפות שונות -- יוונית, תורכית, ערבית, עברית -- יש קידודים שונים במשפחת ISO-8859, ושפה אחת בד"כ לא ניתן לייצג בקידוד המיוחד לשפה אחרת.
+
+ברירת המחדל של גרש היא פקודת המאיית "ispell -a" וקידוד "ISO-8859-1". ברירות מחדל אלה מתאימות אך ורק לשפות לטיניות, כדוגמת צרפתית, איטלקית וספרדית, ולא לשפות כעברית, רוסית וערבית.
+
+את פקודת המאיית יש לציין באמצעות האופציה ‎`--speller-cmd'‎, ואת קידוד המאיית יש לציין באמצעות האופציה ‎`--speller-encoding'‎. לדוגמה, אם נרצה להשתמש במאיית העברי hspell, נפעיל את גרש כך:
+
+$ geresh --speller-cmd="hspell -a -n -i" --speller-encoding=iso-8859-8
+
+(מוטב, כמובן, לכתוב את האופציות הללו בקובץ gereshrc ולא להקלידן שוב ושוב עם הפעלת גרש. לפרטים נוספים, ראה סעיף "הקובץ gereshrc".)
+
+את פקודת המאיית ואת קידודו ניתן להזין גם בצורה אינטראקטיבית, מתוך גרש. לפרטים נוספים, ראה סעיף "טעינה והסרה של המאיית".
+
+בחירה שגויה של הקידוד תגרום ברוב המקרים לפעולתו המשובשת של המאיית. לדוגמה, אם נשמיט את האופציה ‎`--speller-encoding'‎ בדוגמה שהובאה לעיל, גרש לא ישלח למאיית אותיות עבריות, משום שלא ניתן לייצגן בקידוד ברירת המחדל (ISO-8859-1), וכתוצאה מכך המאיית העברי לא ידווח על שום שגיאות איות במלים עבריות.
+
+הדוגמה ההפוכה קיימת גם כן: אם בקובץ gereshrc ציינו את ISO-8859-8 כקידוד המאיית, אולם בשורת הפקודה ציינו את "ispell -a" כפקודת המאיית, המאיית יתריע על שגיאת איות בכל מלה עברית, כי מנקודת מבטו מדובר באותיות לטיניות למהדרין.
+
+עבודה עם המאיית
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כדי לפתוח בבדיקת איות המסמך עומדות לרשות המשתמש שלוש פקודות:
+
+1. כדי לבדוק את איות המסמך כולו נקיש F5.
+
+2. כדי לבדוק את איות המסמך החל מהמלה עליה עומד הסמן ועד סוף המסמך נקיש F6.
+
+3. כדי לבדוק רק את איות המלה עליה (או מיד אחריה) עומד הסמן נקיש M-$‎.
+
+בסיום הבדיקה חוזר הסמן למקומו המקורי, אלא אם כן הפסקנו את הבדיקה באמצעות C-c או C-g או פעמיים ESC (מקשים המשמשים בגרש לביטול פעולות), שאז ימוקם הסמן במקום שגיאת האיות האחרונה. הקשת F6 תמשיך את בדיקת האיות ממקום הסמן.
+
+כאשר גרש נתקל בשגיאת איות, נפתח חלון בתחתית המסך ובו רשימה של הצעות לאיות נכון למלה השגויה. לצד כל הצעה מוצג מספר מ־1 עד 9, או תו אחר, שהקשה עליו תגרום לקבלת ההצעה. עוד מוצגת בחלון שורת עזרה המפרטת את הפקודות האפשריות:
+
+1. מקש הרווח מאפשר להתעלם כעת מהמלה השגויה ולהמשיך הלאה בבדיקת האיות.
+
+2. מקש 'r' מאפשר לערוך ידנית את המלה השגויה. הדבר שימושי במקרים בהם המאיית לא מציע איות נכון למלה.
+
+3. מקש 'a' מוסיף את המלה ל"מילון האישי" שלנו. זהו בד"כ קובץ טקסט טהור בספריית הבית של המשתמש (והוא באחריותו של המאיית, לא של גרש). המאיית יכיר כנכונה כל מלה המופיעה במילון האישי.
+
+4. מקש 'g' גורם לכך שהפקודה שנבצע מיד אח"כ תתבצע אוטומטית גם בעתיד. למשל, במקום להקיש שוב ושוב על מקש הרווח כדי להתעלם ממלה מסוימת, נקיש 'g' ואח"כ על מקש הרווח כדי להתעלם מהמלה המסוימת הזו למשך כל מושב העריכה (בדומה לפעולת "Ignore All" של עורכים אחרים). הקשת 'g' תקֵיפה גם לגבי בחירת איות שמציע המאיית למלה שגויה מסוימת (בדומה לפעולת "Replace All" של עורכים אחרים).
+
+ניתן לבטל את התיקונים שעשינו במסמך באמצעות פקודת החרטה (undo). הקשה על C-u תבטל את השינוי שעשינו במלה האחרונה. יש להמשיך ולהקיש C-u כדי לבטל את כל השינויים.
+
+לדוגמה, אם במהלך בדיקת איות טעינו ובחרנו איות לא מתאים שהציע המאיית, נקיש C-c כדי לעצור את פעולת המאיית, נקיש C-u כדי לבטל את השינוי שנעשה במלה האחרונה (הסמן יחזור למלה זו), ונקיש F6 כדי להמשיך ולבדוק את איות המסמך ממקום הסמן.
+
+טעינה והסרה של המאיית
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+האות 'S', למטה בשורת הסטטוס, מודיעה לנו שהמאיית טעון (loaded) וממתין ברקע לפניות גרש. בדומה ל־emacs, גרש אינו מסיר אוטומטית את הליך (process) המאיית בסיום סריקת המסמך. הדבר שימושי במיוחד כאשר אנו משתמשים במחשב אטי שבו טעינת המאיית אורכת שניות רבות.
+
+גרש מאוד גמיש בכל הנוגע לטעינה ולהסרה של המאיית: הקשה על M-S (שים לב: אות "S" גדולה, לא קטנה) מאפשרת לנו להסיר (unload) את המאיית מהזיכרון. הקשה על M-S כאשר המאיית אינו טעון מאפשרת לנו לערוך את שורת הפקודה של המאיית (command line) ואת קידודו ואח"כ לטעון את המאיית. כך ניתן לשנות, מתוך גרש, את צורת הפעולה של המאיית. לדוגמה, מאייתים רבים מכירים מסמכים מסוגים שונים, כגון HTML, TeX ו־Email, ובחירת הסוג נעשית באמצעות ארגומנטים של שורת הפקודה.
+
+המאיית העברי hspell
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+‏hspell הוא מאיית עברי חופשי מצוין, היחיד מסוגו, שנכתב ע"י נדב הראל ודן קניגסברג. הכל על hspell ניתן לקרוא ב:
+
+<http://www.ivrix.org.il/projects/spell-checker/>
+
+אם גרש מגלה ש־hspell מותקן במערכת (ע"י מציאת תוכנית השירות multispell ב־PATH), ואם המשתמש לא ציין מפורשות את פקודת המאיית, גרש ישתמש ב־hspell עם כל ההגדרות הנכונות (כמו קידוד), כך שנחסך ממך הצורך לקנפג את גרש בעצמך.
+
+נכון לזמן כתיבת שורות אלה, hspell (גרסה 0.8) עדיין לא תומך ב"מילון אישי", ולכן בפעם הבאה שנפעיל את גרש נגלה ש־hspell מדווח כשגויות מלים שבעבר כביכול הוספנו למילון.
+
+גרש אינו שולח למאיית מילים מְנֻקָּדוֹת, כדי לא לבלבל את hspell, ואת המקף העברי, שאותו לא ניתן לייצג ב־iso-8859-8, הוא שולח כתו ASCII "מינוס" (hspell יודע לנצל תו זה).
+
+צביעה תחבירית
+‾‾‾‾‾‾‾‾‾‾‾‾‾
+עורכי טקסט מודרניים יודעים לצבוע את הטקסט המוצג על המסך בהתאם למבנה התחבירי שלו. כאשר נערוך טקסט בשפת "C", יוצגו הערות, לדוגמה, בצבע שונה מטקסט שאינו הערות, וכאשר נערוך טקסט בשפת HTML, יוצגו תגי ה־HTML בצבע שונה מאשר הטקסט המוכל בתוכם. צביעה זו נקראת "צביעה תחבירית" (syntax highlighting) והיא כלל וכלל אינה למטרות אסתטיות, אלא למטרות תועלתיות: הצביעה מקלה על המשתמש להבחין במבנה התחבירי של הטקסט וכך מגבירה את הפרודוקטיביות של המשתמש.
+
+גרש תומך כעת בצביעה תחבירית של HTML ושל הודעות דואר. גרסאות קודמות של גרש לא תמכו בזאת, אך במהלך הזמן נוכחתי לדעת שצביעה תחבירית של שני מבנים אלה, ובעיקר של HTML, היא דבר שלא ניתן לוותר עליו. גרש אינו תומך בצביעה תחבירית של מבנים אחרים (כגון C, Java, PHP), משום שהוא עורך טקסט פשוט, ומן הסתם אף אחד לא ישתמש בו לעריכת קוד מקור של תוכניות מחשב.
+
+כאשר גרש סבור שהטקסט הוא מסוג HTML, הוא יציג בהבלטה את התגים. גרש יודע לזהות רק תגים המתחילים ומסתיימים באותה שורה. זה אומנם חסרון, אבל זה עדיף מכלום (זכור שצביעה תחבירית אינה למטרות אסתטיות, אלא לתועלת בלבד, ולכן החסרון הזה אינו משמעותי במיוחד, לדעתי). כאשר גרש סבור שהטקסט הוא הודעת דואר, הוא יציג בהבלטה כל שורה הפותחת בתו ">" (עם רווחים אפשריים לפניו).
+
+איך יודע גרש מהו סוג הטקסט? אם מצב זיהוי אוטומטי מופעל (זוהי ברירת המחדל), גרש יזהה כ־HTML כל קובץ אשר שמו מכיל את המחרוזת "htm." או "HTM.", וכן כל טקסט המכיל את המחרוזת "HTML>" או "html>" ב־5 השורות הראשונות שלו. כהודעת דואר יזוהה כל טקסט אשר לפחות 2 מתוך 10 השורות הראשונות שלו פותחות בתו ">" (עם רווחים אפשריים לפניו).
+
+אם מצב זיהוי אוטומטי אינו מופעל, ניאלץ לומר לגרש מפורשות מהו סוג הטקסט.
+
+השליטה בצביעה התחבירית נעשית באמצעות תת־תפריט "Syntax Highlighting" אשר בתפריט "Display". קיימות גם אופציות של שורת פקודה.
+
+הדגשת *טקסט* ו_טקסט_
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+הדגשה של טקסט באמצעות תווי "*" או "_" היא מנהג עתיק יומין המקובל בכל מקום בו מנוהלת תכתובת בטקסט טהור, כמו בדואר אלקטרוני וב־newsgroups.
+
+גרש, בברירת מחדל, יציג בהבלטה (ע"י קו תחתי -- אך תוכנות מסוף שונות מתוכנתות להציג זאת באופנים שונים) כל טקסט התחום בשני תווים אלה. השליטה באפשרות זו נעשית גם כן באמצעות תת התפריט "Syntax Highlighting". קיימת גם אופציה של שורת פקודה.
+
+אם משני צידי התו המשמש להדגשה מצויים תווי RTL אלפא־נומריים, ההדגשה לא תתבצע, וזאת כדי למנוע הדגשה בלתי־רצויה של טקסט כמו "http‎://host/show_bug.cgi" או "ticks_per_seconds". אין סייג כזה לגבי תווים אלפא־נומריים בעברית, כי בעברית נהוגות אותיות שימוש המצטרפות למלים (דוגמה בכותרת של סעיף זה), ולכן אפשרי מצב בו אותיות עבריות מצויות משני צידי התו המשתמש להדגשה.
+
+ערכות צבעים
+‾‾‾‾‾‾‾‾‾‾‾
+רבות מתוכנות המסוף של יוניקס לא מאפשרות למשתמש לשנות את הצבעים בהם נעשה שימוש על המסך; רבות מהן כלל אינן משתמשות בצבעים. גרש אינו כזה. גרש מגיע עם מספר ערכות צבעים לבחירתך (אם מוכר לך המונח themes או color schemes מתוכנות אחרות, דע שמדובר באותו דבר), ואם לא תמצא בהן אחת לטעמך, תוכל ליצור ערכה אישית משלך.
+
+ערכות הצבעים מוגדרות בקבצים בעלי סיומת "thm" הנמצאים בספריית ‎/usr/share/geresh/themes/‎ או בספריית ‎$HOME/.geresh/themes/‎ (הפעל את גרש עם האופציה ‎`-V'‎ כדי לגלות את המיקום המדויק). הכלל הוא שקובץ הנמצא בספריית הבית "יסתיר" קובץ בעל שם זהה הנמצא בספריה השיתופית (כלל זה נכון, אגב, גם לגבי כל שאר קובצי ההגדרות של גרש).
+
+שינוי ערכת הצבעים הפעילה נעשה באמצעות תת התפריט "Color scheme" אשר בתפריט "Display". קיימת גם אופציית שורת הפקודה ‎`--theme'‎ (וראה גם סעיף "הקובץ gereshrc").
+
+ערכת ברירת המחדל היא default.thm עבור מסופים צבעוניים ו־default_bw.thm עבור מסופים שאינם צבעוניים.
+
+חלק מהערכות הנן transparent (לרוב הדבר מצוין בסוגריים לצד שמן). פירוש הדבר הוא שצבע הרקע (ולרוב גם צבע הטקסט) של תיבת העריכה הוא צבע הרקע של המסוף ולאו דווקא הצבע השחור. מצב זה, בו צבע הרקע וצבע הטקסט של המסוף אינם שחור ולבן, בהתאמה, שכיח בעיקר בתוכנות אמולצית מסוף תחת X (כגון xterm). חלק מתוכנות אמולצית המסוף מסוגלות גם להציג תמונה ברקע, וכאשר נשתמש בערכת transparent נוכל לראות אותה מאחורי הטקסט.
+
+למידע על פורמט קובצי ה־"thm", עיין בקובץ README.themes.
+
+תקשורת עם תוכנות אחרות
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+הפעלת עורך חיצוני
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+מכיוון שגרש הנו עורך טקסט פשוט למדי, נרצה מדי פעם לעבור לעורך טקסט משוכלל יותר, כגון Vi או Emacs, כדי לבצע מטלות עריכה מורכבות שאינן אפשריות בגרש.
+
+גרש מאפשר לך לעבור לעורך הטקסט החביב עליך בלחיצת מקש. להלן נכנה עורך זה "עורך חיצוני" או סתם "עורך". שמו של הקובץ שעליו אתה עובד יועבר כפרמטר לעורך החיצוני, וכשתסיים את עבודתך בו, תוחזר לגרש והקובץ ייטען מחדש מהדיסק.
+
+הפעלת העורך החיצוני נעשית בלחיצה על F8 או על M-F8. הפקודות הללו מצויות גם תחת תפריט "File" (ראה "Launch external editor"). לחיצה על M-F8 תאפשר לך להזין את פקודת העורך, ואילו לחיצה על F8 תשתמש בפקודת העורך שכבר הזנת קודם. את פקודת העורך ניתן לציין גם באמצעות אופצית שורת הפקודה ‎`--external-editor'‎ (וראה גם סעיף "הקובץ gereshrc").
+
+הערה: המקשים M-F1 עד M-F12 משמשים ב־console של Linux ו־FreeBSD למעבר בין virtual terminals, ולכן במקום M-F8 יש ללחוץ על ESC ואח"כ על F8.
+
+אם לא ציינת את פקודת העורך, ייקבע עורך ברירת המחדל כדלקמן:
+
+1. תוכן משתנה הסביבה EDITOR, אם זה קיים; או
+
+2. gvim, אם התוכנית נמצאת ב־PATH ואם משתנה הסביבה DISPLAY קיים; או
+
+3. התוכנית הראשונה שתימצא ב־PATH, בסדר זה: vim, emacs, pico, vi.
+
+הפעלת עורך חיצוני: מידע נוסף
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כל העורכים שצוינו לעיל מסוגלים לקבל כפרמטר את השורה בה ימוקם הסמן מיד לאחר פתיחת הקובץ. ואכן, אם גרש מוצא שפקודת העורך מכילה אחת מהמחרוזות לעיל (‎"vim", "emacs", "pico, וכו'), הוא יוסיף לה גם את אופצית שורת הפקודה שתמקם אותך בשורה שבה היית בגרש. למשל, אם פקודת העורך היא "vi", יריץ גרש את הפקודה:
+
+vi +12 filename.txt
+
+ולא סתם:
+
+vi filename.txt
+
+(הנחנו, כמובן, שאתה עומד על שורה מספר 12.)
+
+ל־vim (ולווריאציות השונות שלו, כגון gvim) מעניק גרש טיפול מיוחד. אם גרש מוצא את המחרוזת "vim" איפושהו בפקודת העורך החיצוני, גרש יוסיף לפקודה את ההנחיה הדרושה למיקום הסמן גם בעמודה בה היה, וכן הנחיות שיאפשרו לגרש עצמו למקם את הסמן במקום בו היה בעורך החיצוני עם תום העבודה בו. ה"קסם" הזה נעשה ע"י הפעלת vim באופן הבא:
+
+vim -c YYY -c "normal 0" -c "normal XXXl" \
+ -c "au BufUnload filename.txt call \
+ system('echo '.line('.').' '.col('.').' >/tmp/fileMtMJvy')" \
+ filename.txt
+
+כאשר YYY ו־XXX מייצגים את מיקום הסמן (שורה ועמודה) ו־filename.txt את שם הקובץ.
+
+הפקודה הנ"ל חושפת מנגנון שאותו מציע גרש לשימושם של אלה הרוצים לשמור על מיקום הסמן במעבר אל העורך החיצוני, וממנו אל גרש. פרטי המנגנון:
+
+לפני שגרש קורא לעורך החיצוני הוא כותב אל קובץ מסוים (קובץ זמני, בדר"כ בספריית ‎/tmp) את מספר השורה והעמודה בהם עומד הסמן. גרש יוצר משתנה סביבה בשם GERESH_CURSOR_FILE המכיל את נתיבו של הקובץ. רק אח"כ קורא גרש לעורך החיצוני. לאחר שמסתיימת העבודה עם העורך החיצוני קורא גרש את הקובץ הנ"ל וממקם את הסמן על פיו. הכוונה היא ש־script מצד העורך יעשה שימוש בקובץ זה כדי למקם את הסמן במקום המתאים מיד עם פתיחת הקובץ, או כדי ליידע את גרש אודות המיקום בו היה הסמן טרם עזיבת העורך.
+
+צינורות
+‾‾‾‾‾‾‾
+צינור (pipe) הוא הליך (process) שאליו התוכנה שלנו מזינה מידע (קלט), או שממנו היא קוראת מידע (פלט). בכל מקום שבו ניתן לציין שם קובץ אפשר גם לציין צינור: פקודה של מערכת ההפעלה הפותחת בתו "|". התו "|" מיידע את גרש שמדובר בפקודה שעליו להריץ ולא בקובץ שעליו לפתוח.
+
+ניתן להשתמש גם בתו "!" במקום ב־"|". השימוש בתו "!" מקובל בעורכים המסורתיים vi ו־ex (במדריך זה העדפתי להשתמש דווקא ב־"|" כדי שהמשתמש לא יסבור בטעות שמדובר במשפטי קריאה).
+
+נדגים את ההבדל בין קובץ לבין צינור בעזרת דוגמה: אם נקיש M-r (פקודת "הכנס קובץ") ונקליד כשם הקובץ "date", גרש יכניס את תוכן הקובץ ששמו date אל החוצץ; אולם אם נקליד "‎|date", גרש יריץ את פקודת מערכת ההפעלה date ויכניס את הפלט שלה (התאריך הנוכחי) אל החוצץ.
+
+גרש תמיד מתַקשר עם הצינור בקידוד UTF-8. אם התכנית שבה אתה משתמש מצפה לקידוד אחר, השתמש בתכניות כדוגמת iconv או recode לביצוע ההמרה.
+
+(בשל הנאמר לעיל, אם נקליד "‎|date" כאשר LANG מוגדר כ־he_IL, גרש ינסה להמיר טקסט עברי המקודד ב־ISO-8859-8 (שמות הימים והחודשים) כאילו היה מקודד ב־UTF-8 והדבר יגרום לו להיכשל. כדי לפתור את הבעיה, נקליד "‎|LC_ALL=C date" או "‎|date|iconv -f iso-8859-8 -t utf-8")
+
+אם טעינו בהקלדת הפקודה והתוכנית שהרצנו גרמה לקשקוש התצוגה, נקיש C-l כדי לרענן את המסך.
+
+ה־clipboard של X11
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+אחד היתרונות בעבודה תחת X11 הוא היכולת להעביר בקלות טקסט מיישום אחד לאחר באמצעות ה־clipboard (להלן: סלסלה). גרש אינו תומך ישירות בסלסלה, אך אפשר לגשת אליה בעקיפין באמצעות הכלים שגרש מספק.
+
+גרש מאפשר לכתוב בלוק (selection) אל קובץ (C-w), והוא גם מאפשר להכניס קובץ אל החוצץ (M-r). נניח שמותקנת במערכת תכנית שירות בשם uclip, המעתיקה את הקלט שהיא מקבלת אל הסלסלה כאשר היא מופעלת עם האופציה ‎`-i'‎, ואשר מדפיסה את תוכן הסלסלה כאשר היא מופעלת עם האופציה ‎`-o'‎. מכיוון שגרש מאפשר לציין צינור במקום שם קובץ, נוכל לכתוב "‎|uclip -iu" ו־"‎|uclip -ou" במקום שם הקובץ לכתיבה ולהכנסה, בהתאמה, כדי להעתיק ולהדביק טקסט אל הסלסלה וממנה (האופציה ‎`-u'‎, שגם אותה ציינתי בפקודה, מנחה את uclip לבצע את הקלט והפלט בקידוד UTF-8, והדבר נחוץ משום שגרש תמיד מתקשר עם הצינור בקידוד זה).
+
+מכיוון שלשורת הדיאלוג יש היסטוריה, אין צורך לכתוב שוב ושוב "‎|uclip -iu" ו־"‎|uclip -ou". די להקיש חץ למעלה כדי להחזיר את השורה האחרונה שהוזנה.
+
+התכנית uclip אכן קיימת. כתב אותה עבדכם הנאמן, ותוכלו להורידה מהכתובת:
+
+<http://www.typo.co.il/~mooffie/uclip/>
+
+קיימות תכנות דומות נוספות, אך הייחוד של uclip הוא שהיא תומכת ב־Unicode/locale ולכן תומכת גם בעברית (בכל השפות, למעשה). תכנות אחרות שבדקתי לא תומכות בעברית.
+
+גרש כ־pager
+‾‾‾‾‾‾‾‾‾‾‾
+לעתים נרצה להשתמש בגרש רק לשם צפייה בקבצים. מקובל לכנות תוכנה מסוג זה viewer או pager. בעזרת האופציה ‎`-R'‎ או ‎`-v'‎ נבטיח שלא ניתן יהיה לשנות את החוצץ (כלומר, מצב read-only). במצב זה ניתן לצאת מגרש גם בהקשה על 'q'. גרש מסופק עם סקריפט קטנטן (shell script) בשם pgeresh המאפשר לנו לכתוב פקודות כ:
+
+$ ls | pgeresh | mail
+
+הפקת פלט בעברית ויזואלית
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+לאחר שנערוך מסמך בגרש ייתכן שנרצה להדפיסו או לשלחו כפקס. תכנות הדפסה ופקס במערכות יוניקס בד"כ אינן תומכות ישירות בעברית לוגית, אך ברוב המקרים אפשר להזין להן את המסמך בפורמט הידוע בכינוי "עברית ויזואלית". בפורמט זה העברית במסמך כבר הפוכה, כך שתוכנת היעד כבר אינה צריכה לתמוך באלגוריתם ה־BiDi, וכמו כן הפסקאות כבר שבורות לשורות המיושרות לימין באמצעות הוספה של תווי רווח בצידן השמאלי. במלים אחרות, תוכנת היעד מקבלת את הטקסט כשהוא כבר "לעוס".
+
+כאשר גרש מופעל עם האופציה ‎`--log2vis'‎ (או, בשמהּ הקצר, ‎`-p'‎), גרש ימיר את המסמך הלוגי, ששמו צוין בשורת הפקודה, או שהוזן דרך stdin, למסמך בפורמט ויזואלי, שיכתב ל־stdout. מבחינת צורת ההפעלה הזו, גרש זהה לשאר הפילטרים (filters) במערכת יוניקס.
+
+באופן כללי, הפלט הויזואלי יהיה דומה למה שיראה המשתמש על המסך אם יפעיל את גרש כרגיל (כתוכנה אינטראקטיבית). כל אופציות שורת הפקודה של גרש זמינות גם כאשר גרש מופעל כפילטר. למשל, ניתן להשתמש באופציה ‎`--dir-algo'‎ כדי לבחור את האלגוריתם שעל פיו תיקבע כיווניות הפסקאות, באופציה ‎`-f'‎ כדי לציין מפורשות את קידוד הקלט, ובאופציה ‎`-T'‎ כדי לקבוע את רוחב תווי ה־TAB. בנוסף, מספר אופציות רלוונטיות רק כאשר גרש מופעל כפילטר:
+
+האופציה ‎`-w'‎ מאפשרת לקבוע את רוחב הפלט: את אורכן המקסימלי של השורות בו. פסקאות ארוכות ישברו (wrap) לשורות קצרות שייושרו לימין ע"י הוספת תווי רווח בצידן השמאלי. אם לא נציין את רוחב הפלט, גרש יקרא אותו ממשתנה הסביבה COLUMNS, ואם גם זה לא קיים, ברירת המחדל היא 80 תווים. אם נציין ‎`-w0'‎, לא תתבצע שבירת פסקאות.
+
+האופציה ‎`-t'‎ מאפשרת לקבוע את קידוד הפלט. אם לא נציין את קידוד הפלט, הוא יהיה כקידוד הקלט. (אגב, גם לתכנית iconv אופציות בשם ‎`-t'‎ ו־‎`-f'‎, והן משמשות למטרה זהה. בכתיבת גרש השתדלתי להשתמש בשמות אופציות ובצירופי מקשים המקובלים בתכנות אחרות כדי להקל על זכירתם.)
+
+האופציה ‎`--log2vis-options'‎ (או, בשמהּ הקצר, ‎`-E'‎), מאפשרת להעביר לגרש הנחיות לצורך שליטה רבה יותר על תהליך ההמרה. למשל, ההנחיה "bdo" תדריך את גרש להוסיף תו LRO בתחילת כל שורה ותו PDF בסופה. הנחיה זו שימושית במקרים בהם נרצה לנטרל את תמיכת ה־BiDi, אם קיימת, בתוכנת היעד שאליה אנו מזינים את הפלט הויזואלי (למשל, בתכנית uniprint של Yudit בגרסאותיה האחרונות).
+
+הדוגמה הבאה מראה איך להפיק קובץ postscript מהקובץ mydoc.txt:
+
+$ geresh --log2vis --log2vis-options=bdo -w 50 \
+ -t utf-8 mydoc.txt > mydoc_vis.txt
+$ uniprint -font courier.ttf -left \
+ -in mydoc_vis.txt -out mydoc.ps
+
+בדוגמה השתמשנו בתכנית uniprint, שהיא חלק מחבילת Yudit, כדי להפיק את קובץ ה־postscript. יש לשים לב לארבעה דברים: השתמשנו בהנחיה "bdo" כי ב־uniprint כבר יש תמיכה ב־BiDi, תמיכה שדווקא תגרום לעיוות לא־רצוי של הטקסט. מסיבה דומה השתמשנו באופציה ‎`-left'‎ של uniprint כדי שהתוכנה לא תיישר שורות המכילות תווים בעברית לימין. כמו כן השתמשנו בפונט בעל ריווח קבוע, courier.ttf. (אם נרצה להשתמש בפונט בעל ריווח יחסי, נשתמש באופציה ‎`-right'‎ של uniprint, אך התוצאה לא תהיה מוצלחת במיוחד, כי כאשר גרש חותך פסקאות לשורות הוא עושה זאת על פי מספר התווים (ליתר דיוק, combining-characters רוחבם 0 ו־wide-characters רוחבם 2) ולא על פי רוחבם בפונט בו אנו עתידים להשתמש; גרש אינו יודע דבר על פונטים.) לבסוף, השתמשנו באופציה ‎`-t'‎ כדי לומר לגרש לכתוב את הפלט בקידוד UTF-8 גם אם הקלט אינו כזה, כי זה מה ש־uniprint מצפה לקבל.
+
+לקבלת רשימת כל האופציות וההנחיות שגרש מקבל, הפעל את גרש עם האופציה ‎`-h'‎.
+
+קו תחתי בפלט הויזואלי
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+לעתים מתעורר הצורך להדגיש מלה או קטע מסוים בקו־תחתי כאשר אנו מדפיסים מסמך. אמנם, מעבדי תמלילים מודרניים מאפשרים לעשות הרבה יותר: לשנות את עובי האותיות, את גודלן ואת צבען, אולם אנו נדון רק בקו־תחתי, שהוא המינימום הדרוש להדפסת מסמכים, והוא במסגרת האפשר באמצעים הטכניים הפשוטים העומדים לרשותנו.
+
+שתי דרכים למתוח קו תחתי:
+
+1. להזין לטקסט את התו U+332, שהוא גליף "קו־תחתי" ברוחב אות אחת. תו זה מטבעו אינו שונה מסימני הניקוד ומהסימנים הדיאקריטיים. החסרונות בשיטה זו: (1) אנו "הורסים" את המלים כאשר אנו מפרידים בין אותיותיהן בתווים זרים; (2) זוהי מלאכה מיגעת: נצטרך להזין את התו עבור כל אות שתחתיה נרצה למתוח קו, ואם נחליט שאיננו רוצים קו תחתי, נצטרך למחוק את התווים.
+
+2. ניתן להנחות את גרש להוסיף בעצמו את התו U+332, בשעת ההמרה הויזואלית, לכל התווים הנמצאים בין שתי כוכביות. למשל, *המלה* הזו תופיע בפלט הויזואלי עם קו תחתי. אם אחרי הכוכבית הראשונה מצוי תו רווח, גרש יתעלם מהכוכבית. זה משום שלעתים משתמש מקליד כוכבית בתחילת פסקה כדי לציין סעיף חדש (bullet). ניתן להשתמש גם בתו "_" במקום בכוכבית. למעשה, גרש גמיש יותר ממה שהוסבר כרגע: גרש מאפשר למשתמש לבחור את התו שישמש לסימון וגרש מאפשר לבחור את התו, הגליף, שישמש כדי להפיק קו־תחתי. למשל, אם בטקסט שלנו כבר עשינו שימוש רב בכוכביות למטרות אחרות, נוכל להנחות את גרש להתייחס דווקא לתו "^" כאל תו התוחם קטע מודגש, ואם בפונט שבו אנו מדפיסים את המסמך לא מצוי גליף עבור התו U+332, נוכל להנחות את גרש להשתמש, לדוגמה, בתו U+5B7, שהוא סימן הניקוד העברי "פתח".
+
+את ההנחיות הללו יש להעביר לגרש באמצעות האופציה ‎`--log2vis-options'‎. כדי להנחות את גרש למתוח קו־תחתי, יש להעביר את המחרוזת "emph" (קיצור של emphasize); ניתן להצמיד למחרוזת זו שני מספרים, המופרדים בנקודתיים: הראשון קובע את מספרו של התו שהגליף שלו ישמש להפקת הקו־התחתי, והשני הוא מספרו של התו שישמש לסימון המלה או הקטע המודגש. דוגמה:
+
+$ geresh --log2vis --log2vis-options=emph:0x5B7:0x5E,bdo -t utf-8 myfile.txt
+
+בדוגמה לעיל בחרנו להשתמש בתו "^" לסימון (ערכו ההקסדצימלי 5E), ובסימן הניקוד העברי "פתח" (ערכו 5B7) להפקת קו־תחתי. בדוגמה השתמשנו גם בהנחיה "bdo", אך זאת רק כדי להדגים שניתן להעביר לגרש מספר הנחיות. בחרנו גם לכתוב את הפלט בקידוד UTF-8, כי אחרת קידוד הפלט יהיה כקידוד הקלט, ואם בקידוד זה לא ניתן לייצג את גליף הקו־התחתי שבו בחרנו, גרש ידפיס הודעת שגיאה ויעצור.
+
+דוגמה נוספת, להרצה בחלון xterm:
+
+$ echo "the following _word_ is underlined." | geresh -p -Eemph -tutf8
+
+נסה להסיר את האופציה ‎`-tutf8'‎ (אשר שקולה ל־‎`-t utf-8'‎), והסבר את התוצאה.
+
+הקובץ gereshrc
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+כפי שראינו, לגרש הרבה פרמטרים שניתן לשנות (כמו רוחב TAB, עמודת יישור, קידוד ברירת מחדל) ולא מעט יכולות שאפשר להפעיל (enable) או לבטל (disable). כל זאת ניתן לעשות מתוך גרש עצמו, באמצעות שימוש בתפריט או בצירופי מקשים, אך גם באמצעות אופציות לשימוש בשורת הפקודה.
+
+גרש מאפשר לנו לאחסן אופציות בקובץ ‎~/.geresh/gereshrc. כאשר אנו מאחסנים את האופציות בקובץ זה, אנו פטורים מהצורך לציינן בשורת הפקודה שוב ושוב. ניתן לכתוב כל אופציה בשורה נפרדת, כדי להקל על עצמנו את הבנת הכתוב, וניתן להקדים כל שורה בתו "#" כדי להפוך אותה להערה.
+
+לקבלת רשימת האופציות המלאה שגרש מקבל, הפעל את גרש עם האופציה ‎`-h'‎.
+
+בעת אתחולו קורא גרש אופציות, בסדר זה, מ:
+
+1. מהקובץ ששמו מופיע במשתנה הסביבה geresh_RC. אם משתנה הסביבה או הקובץ לא קיימים, מהקובץ ‎~/.geresh/gereshrc, ואם גם זה לא קיים, מהקובץ ‎/usr/share/geresh/gereshrc.
+
+2. משורת הפקודה.
+
+3. ממשתנה הסביבה geresh_ARGS.
+
+לקבלת רשימת הקבצים שגרש קורא, הפעל את גרש עם האופציה ‎`-V'‎.
+
+[סוף]
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..dfb7fef
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,78 @@
+AUTOMAKE_OPTIONS = foreign
+
+bin_PROGRAMS = geresh
+geresh_SOURCES = \
+ bidi.cc bidi.h \
+ basemenu.cc basemenu.h \
+ converters.cc converters.h \
+ dbg.cc dbg.h \
+ dialogline.cc dialogline.h \
+ editbox.cc editbox2.cc editbox.h \
+ editor.cc editor.h \
+ event.cc event.h \
+ helpbox.cc helpbox.h \
+ inputline.cc inputline.h \
+ io.cc io.h \
+ iso88598.cc iso88598.h \
+ label.cc label.h \
+ main.cc bindings.cc \
+ menus.cc menus.h \
+ mk_wcwidth.cc mk_wcwidth.h \
+ question.cc question.h \
+ scrollbar.cc scrollbar.h \
+ speller.cc speller.h \
+ shaping.cc shaping.h \
+ statusline.cc statusline.h \
+ terminal.cc terminal.h \
+ themes.cc themes.h \
+ transtbl.cc transtbl.h \
+ types.cc types.h \
+ undo.cc undo.h \
+ utf8.cc utf8.h \
+ widget.cc widget.h \
+ directvect.h dispatcher.h my_wctob.h \
+ pathnames.h point.h univalues.h
+
+bin_SCRIPTS = pgeresh
+
+THEME_DIR = themes
+THEMES = \
+ $(THEME_DIR)/default.thm \
+ $(THEME_DIR)/default_bw.thm \
+ $(THEME_DIR)/mc.thm \
+ $(THEME_DIR)/green.thm \
+ $(THEME_DIR)/borland.thm \
+ $(THEME_DIR)/occult.thm \
+ $(THEME_DIR)/arnold.thm \
+ $(THEME_DIR)/README.themes
+
+pkgdata_DATA = \
+ MANUAL.he kbdtab reprtab transtab
+
+EXTRA_DIST = \
+ MANUAL.he kbdtab reprtab transtab \
+ $(THEMES)
+
+localedir = $(datadir)/locale
+INCLUDES = -DLOCALEDIR=\"$(localedir)\" -DPKGDATADIR=\"$(pkgdatadir)\"
+
+# Hmmm.... what's the correct way to create a directory
+# under pkgdatadir? How can I install the themes using only
+# pkgdata_DATA? Meanwhile, I'll use the following hack:
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)/$(THEME_DIR)
+ @list='$(THEMES)'; for p in $$list; do \
+ if test -f $(srcdir)/$$p; then \
+ echo " $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ else if test -f $$p; then \
+ echo " $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ fi; fi; \
+ done
+
+uninstall-local:
+ list='$(THEMES)'; for p in $$list; do \
+ rm -f $(DESTDIR)$(pkgdatadir)/$$p; \
+ done
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..1c285d7
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,573 @@
+# Makefile.in generated automatically by automake 1.4-p5 from Makefile.am
+
+# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+
+SHELL = @SHELL@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+bindir = @bindir@
+sbindir = @sbindir@
+libexecdir = @libexecdir@
+datadir = @datadir@
+sysconfdir = @sysconfdir@
+sharedstatedir = @sharedstatedir@
+localstatedir = @localstatedir@
+libdir = @libdir@
+infodir = @infodir@
+mandir = @mandir@
+includedir = @includedir@
+oldincludedir = /usr/include
+
+DESTDIR =
+
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+
+top_builddir = .
+
+ACLOCAL = @ACLOCAL@
+AUTOCONF = @AUTOCONF@
+AUTOMAKE = @AUTOMAKE@
+AUTOHEADER = @AUTOHEADER@
+
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(AM_INSTALL_PROGRAM_FLAGS)
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+transform = @program_transform_name@
+
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+CC = @CC@
+CPP = @CPP@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+FRIBIDI_CONFIG = @FRIBIDI_CONFIG@
+MAKEINFO = @MAKEINFO@
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+AUTOMAKE_OPTIONS = foreign
+
+bin_PROGRAMS = geresh
+geresh_SOURCES = \
+ bidi.cc bidi.h \
+ basemenu.cc basemenu.h \
+ converters.cc converters.h \
+ dbg.cc dbg.h \
+ dialogline.cc dialogline.h \
+ editbox.cc editbox2.cc editbox.h \
+ editor.cc editor.h \
+ event.cc event.h \
+ helpbox.cc helpbox.h \
+ inputline.cc inputline.h \
+ io.cc io.h \
+ iso88598.cc iso88598.h \
+ label.cc label.h \
+ main.cc bindings.cc \
+ menus.cc menus.h \
+ mk_wcwidth.cc mk_wcwidth.h \
+ question.cc question.h \
+ scrollbar.cc scrollbar.h \
+ speller.cc speller.h \
+ shaping.cc shaping.h \
+ statusline.cc statusline.h \
+ terminal.cc terminal.h \
+ themes.cc themes.h \
+ transtbl.cc transtbl.h \
+ types.cc types.h \
+ undo.cc undo.h \
+ utf8.cc utf8.h \
+ widget.cc widget.h \
+ directvect.h dispatcher.h my_wctob.h \
+ pathnames.h point.h univalues.h
+
+
+bin_SCRIPTS = pgeresh
+
+THEME_DIR = themes
+THEMES = \
+ $(THEME_DIR)/default.thm \
+ $(THEME_DIR)/default_bw.thm \
+ $(THEME_DIR)/mc.thm \
+ $(THEME_DIR)/green.thm \
+ $(THEME_DIR)/borland.thm \
+ $(THEME_DIR)/occult.thm \
+ $(THEME_DIR)/arnold.thm \
+ $(THEME_DIR)/README.themes
+
+
+pkgdata_DATA = \
+ MANUAL.he kbdtab reprtab transtab
+
+
+EXTRA_DIST = \
+ MANUAL.he kbdtab reprtab transtab \
+ $(THEMES)
+
+
+localedir = $(datadir)/locale
+INCLUDES = -DLOCALEDIR=\"$(localedir)\" -DPKGDATADIR=\"$(pkgdatadir)\"
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = config.h
+CONFIG_CLEAN_FILES = pgeresh
+PROGRAMS = $(bin_PROGRAMS)
+
+
+DEFS = @DEFS@ -I. -I$(srcdir) -I.
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+geresh_OBJECTS = bidi.o basemenu.o converters.o dbg.o dialogline.o \
+editbox.o editbox2.o editor.o event.o helpbox.o inputline.o io.o \
+iso88598.o label.o main.o bindings.o menus.o mk_wcwidth.o question.o \
+scrollbar.o speller.o shaping.o statusline.o terminal.o themes.o \
+transtbl.o types.o undo.o utf8.o widget.o
+geresh_LDADD = $(LDADD)
+geresh_DEPENDENCIES =
+geresh_LDFLAGS =
+SCRIPTS = $(bin_SCRIPTS)
+
+CXXFLAGS = @CXXFLAGS@
+CXXCOMPILE = $(CXX) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+CXXLD = $(CXX)
+CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@
+CFLAGS = @CFLAGS@
+COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@
+DATA = $(pkgdata_DATA)
+
+DIST_COMMON = README ./stamp-h.in AUTHORS COPYING INSTALL Makefile.am \
+Makefile.in NEWS THANKS TODO aclocal.m4 config.h.in configure \
+configure.in install-sh missing mkinstalldirs pgeresh.in
+
+
+DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
+
+TAR = gtar
+GZIP_ENV = --best
+SOURCES = $(geresh_SOURCES)
+OBJECTS = $(geresh_OBJECTS)
+
+all: all-redirect
+.SUFFIXES:
+.SUFFIXES: .S .c .cc .o .s
+$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.in $(ACLOCAL_M4)
+ cd $(top_srcdir) && $(AUTOMAKE) --foreign --include-deps Makefile
+
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ cd $(top_builddir) \
+ && CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+$(ACLOCAL_M4): configure.in
+ cd $(srcdir) && $(ACLOCAL)
+
+config.status: $(srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ $(SHELL) ./config.status --recheck
+$(srcdir)/configure: $(srcdir)/configure.in $(ACLOCAL_M4) $(CONFIGURE_DEPENDENCIES)
+ cd $(srcdir) && $(AUTOCONF)
+
+config.h: stamp-h
+ @if test ! -f $@; then \
+ rm -f stamp-h; \
+ $(MAKE) stamp-h; \
+ else :; fi
+stamp-h: $(srcdir)/config.h.in $(top_builddir)/config.status
+ cd $(top_builddir) \
+ && CONFIG_FILES= CONFIG_HEADERS=config.h \
+ $(SHELL) ./config.status
+ @echo timestamp > stamp-h 2> /dev/null
+$(srcdir)/config.h.in: $(srcdir)/stamp-h.in
+ @if test ! -f $@; then \
+ rm -f $(srcdir)/stamp-h.in; \
+ $(MAKE) $(srcdir)/stamp-h.in; \
+ else :; fi
+$(srcdir)/stamp-h.in: $(top_srcdir)/configure.in $(ACLOCAL_M4)
+ cd $(top_srcdir) && $(AUTOHEADER)
+ @echo timestamp > $(srcdir)/stamp-h.in 2> /dev/null
+
+mostlyclean-hdr:
+
+clean-hdr:
+
+distclean-hdr:
+ -rm -f config.h
+
+maintainer-clean-hdr:
+pgeresh: $(top_builddir)/config.status pgeresh.in
+ cd $(top_builddir) && CONFIG_FILES=$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+mostlyclean-binPROGRAMS:
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+distclean-binPROGRAMS:
+
+maintainer-clean-binPROGRAMS:
+
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(bindir)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ if test -f $$p; then \
+ echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`"; \
+ $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`; \
+ else :; fi; \
+ done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ list='$(bin_PROGRAMS)'; for p in $$list; do \
+ rm -f $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`; \
+ done
+
+.c.o:
+ $(COMPILE) -c $<
+
+.s.o:
+ $(COMPILE) -c $<
+
+.S.o:
+ $(COMPILE) -c $<
+
+mostlyclean-compile:
+ -rm -f *.o core *.core
+
+clean-compile:
+
+distclean-compile:
+ -rm -f *.tab.c
+
+maintainer-clean-compile:
+
+geresh: $(geresh_OBJECTS) $(geresh_DEPENDENCIES)
+ @rm -f geresh
+ $(CXXLINK) $(geresh_LDFLAGS) $(geresh_OBJECTS) $(geresh_LDADD) $(LIBS)
+
+install-binSCRIPTS: $(bin_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(bindir)
+ @list='$(bin_SCRIPTS)'; for p in $$list; do \
+ if test -f $$p; then \
+ echo " $(INSTALL_SCRIPT) $$p $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`"; \
+ $(INSTALL_SCRIPT) $$p $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`; \
+ else if test -f $(srcdir)/$$p; then \
+ echo " $(INSTALL_SCRIPT) $(srcdir)/$$p $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`"; \
+ $(INSTALL_SCRIPT) $(srcdir)/$$p $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`; \
+ else :; fi; fi; \
+ done
+
+uninstall-binSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ list='$(bin_SCRIPTS)'; for p in $$list; do \
+ rm -f $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`; \
+ done
+.cc.o:
+ $(CXXCOMPILE) -c $<
+
+install-pkgdataDATA: $(pkgdata_DATA)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
+ @list='$(pkgdata_DATA)'; for p in $$list; do \
+ if test -f $(srcdir)/$$p; then \
+ echo " $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ else if test -f $$p; then \
+ echo " $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ fi; fi; \
+ done
+
+uninstall-pkgdataDATA:
+ @$(NORMAL_UNINSTALL)
+ list='$(pkgdata_DATA)'; for p in $$list; do \
+ rm -f $(DESTDIR)$(pkgdatadir)/$$p; \
+ done
+
+tags: TAGS
+
+ID: $(HEADERS) $(SOURCES) $(LISP)
+ list='$(SOURCES) $(HEADERS)'; \
+ unique=`for i in $$list; do echo $$i; done | \
+ awk ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ here=`pwd` && cd $(srcdir) \
+ && mkid -f$$here/ID $$unique $(LISP)
+
+TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS)'; \
+ unique=`for i in $$list; do echo $$i; done | \
+ awk ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ test -z "$(ETAGS_ARGS)config.h.in$$unique$(LISP)$$tags" \
+ || (cd $(srcdir) && etags $(ETAGS_ARGS) $$tags config.h.in $$unique $(LISP) -o $$here/TAGS)
+
+mostlyclean-tags:
+
+clean-tags:
+
+distclean-tags:
+ -rm -f TAGS ID
+
+maintainer-clean-tags:
+
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+
+# This target untars the dist file and tries a VPATH configuration. Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+ -rm -rf $(distdir)
+ GZIP=$(GZIP_ENV) $(TAR) zxf $(distdir).tar.gz
+ mkdir $(distdir)/=build
+ mkdir $(distdir)/=inst
+ dc_install_base=`cd $(distdir)/=inst && pwd`; \
+ cd $(distdir)/=build \
+ && ../configure --srcdir=.. --prefix=$$dc_install_base \
+ && $(MAKE) $(AM_MAKEFLAGS) \
+ && $(MAKE) $(AM_MAKEFLAGS) dvi \
+ && $(MAKE) $(AM_MAKEFLAGS) check \
+ && $(MAKE) $(AM_MAKEFLAGS) install \
+ && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+ && $(MAKE) $(AM_MAKEFLAGS) dist
+ -rm -rf $(distdir)
+ @banner="$(distdir).tar.gz is ready for distribution"; \
+ dashes=`echo "$$banner" | sed s/./=/g`; \
+ echo "$$dashes"; \
+ echo "$$banner"; \
+ echo "$$dashes"
+dist: distdir
+ -chmod -R a+r $(distdir)
+ GZIP=$(GZIP_ENV) $(TAR) chozf $(distdir).tar.gz $(distdir)
+ -rm -rf $(distdir)
+dist-all: distdir
+ -chmod -R a+r $(distdir)
+ GZIP=$(GZIP_ENV) $(TAR) chozf $(distdir).tar.gz $(distdir)
+ -rm -rf $(distdir)
+distdir: $(DISTFILES)
+ -rm -rf $(distdir)
+ mkdir $(distdir)
+ -chmod 777 $(distdir)
+ $(mkinstalldirs) $(distdir)/$(THEME_DIR)
+ @for file in $(DISTFILES); do \
+ d=$(srcdir); \
+ if test -d $$d/$$file; then \
+ cp -pr $$d/$$file $(distdir)/$$file; \
+ else \
+ test -f $(distdir)/$$file \
+ || ln $$d/$$file $(distdir)/$$file 2> /dev/null \
+ || cp -p $$d/$$file $(distdir)/$$file || :; \
+ fi; \
+ done
+basemenu.o: basemenu.cc config.h basemenu.h widget.h dispatcher.h \
+ event.h types.h directvect.h terminal.h themes.h
+bidi.o: bidi.cc config.h bidi.h types.h directvect.h univalues.h dbg.h
+bindings.o: bindings.cc config.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h bidi.h univalues.h \
+ transtbl.h undo.h point.h editor.h dialogline.h label.h \
+ inputline.h statusline.h speller.h helpbox.h basemenu.h
+converters.o: converters.cc config.h converters.h types.h directvect.h \
+ iso88598.h utf8.h dbg.h
+dbg.o: dbg.cc config.h terminal.h dbg.h
+dialogline.o: dialogline.cc config.h dialogline.h label.h types.h \
+ directvect.h widget.h dispatcher.h event.h terminal.h \
+ inputline.h editbox.h bidi.h univalues.h transtbl.h undo.h \
+ point.h editor.h statusline.h speller.h question.h dbg.h
+editbox.o: editbox.cc config.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h bidi.h univalues.h \
+ transtbl.h undo.h point.h scrollbar.h themes.h dbg.h
+editbox2.o: editbox2.cc config.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h bidi.h univalues.h \
+ transtbl.h undo.h point.h mk_wcwidth.h my_wctob.h iso88598.h \
+ shaping.h themes.h dbg.h
+editor.o: editor.cc config.h editor.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h bidi.h univalues.h \
+ transtbl.h undo.h point.h dialogline.h label.h inputline.h \
+ statusline.h speller.h menus.h basemenu.h scrollbar.h io.h \
+ pathnames.h themes.h utf8.h dbg.h helpbox.h
+event.o: event.cc config.h event.h types.h directvect.h terminal.h \
+ my_wctob.h iso88598.h dbg.h
+helpbox.o: helpbox.cc config.h helpbox.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h bidi.h univalues.h \
+ transtbl.h undo.h point.h label.h editor.h dialogline.h \
+ inputline.h statusline.h speller.h io.h pathnames.h themes.h
+inputline.o: inputline.cc config.h inputline.h editbox.h directvect.h \
+ widget.h dispatcher.h event.h types.h terminal.h bidi.h \
+ univalues.h transtbl.h undo.h point.h io.h themes.h dbg.h
+io.o: io.cc config.h io.h types.h directvect.h editbox.h widget.h \
+ dispatcher.h event.h terminal.h bidi.h univalues.h transtbl.h \
+ undo.h point.h converters.h dbg.h speller.h label.h
+iso88598.o: iso88598.cc config.h iso88598.h types.h directvect.h \
+ univalues.h
+label.o: label.cc config.h label.h types.h directvect.h widget.h \
+ dispatcher.h event.h terminal.h themes.h
+main.o: main.cc config.h io.h types.h directvect.h pathnames.h \
+ terminal.h editor.h editbox.h widget.h dispatcher.h event.h \
+ bidi.h univalues.h transtbl.h undo.h point.h dialogline.h \
+ label.h inputline.h statusline.h speller.h themes.h dbg.h
+menus.o: menus.cc config.h io.h types.h directvect.h pathnames.h menus.h \
+ basemenu.h widget.h dispatcher.h event.h terminal.h editbox.h \
+ bidi.h univalues.h transtbl.h undo.h point.h editor.h \
+ dialogline.h label.h inputline.h statusline.h speller.h
+mk_wcwidth.o: mk_wcwidth.cc mk_wcwidth.h types.h directvect.h
+question.o: question.cc config.h question.h label.h types.h directvect.h \
+ widget.h dispatcher.h event.h terminal.h
+scrollbar.o: scrollbar.cc config.h scrollbar.h types.h directvect.h \
+ widget.h dispatcher.h event.h terminal.h themes.h
+shaping.o: shaping.cc config.h shaping.h types.h directvect.h widget.h \
+ dispatcher.h event.h terminal.h bidi.h univalues.h
+speller.o: speller.cc speller.h editbox.h directvect.h widget.h \
+ dispatcher.h event.h types.h terminal.h config.h bidi.h \
+ univalues.h transtbl.h undo.h point.h label.h mk_wcwidth.h \
+ converters.h editor.h dialogline.h inputline.h statusline.h \
+ dbg.h
+statusline.o: statusline.cc config.h statusline.h editbox.h directvect.h \
+ widget.h dispatcher.h event.h types.h terminal.h bidi.h \
+ univalues.h transtbl.h undo.h point.h editor.h dialogline.h \
+ label.h inputline.h speller.h themes.h dbg.h
+terminal.o: terminal.cc config.h editor.h editbox.h directvect.h \
+ widget.h dispatcher.h event.h types.h terminal.h bidi.h \
+ univalues.h transtbl.h undo.h point.h dialogline.h label.h \
+ inputline.h statusline.h speller.h dbg.h
+themes.o: themes.cc config.h io.h types.h directvect.h pathnames.h \
+ widget.h dispatcher.h event.h terminal.h themes.h
+transtbl.o: transtbl.cc config.h transtbl.h types.h directvect.h io.h \
+ dbg.h
+types.o: types.cc config.h types.h directvect.h converters.h utf8.h
+undo.o: undo.cc undo.h types.h directvect.h point.h dbg.h config.h
+utf8.o: utf8.cc config.h utf8.h types.h directvect.h univalues.h dbg.h
+widget.o: widget.cc config.h widget.h dispatcher.h event.h types.h \
+ directvect.h terminal.h mk_wcwidth.h bidi.h univalues.h \
+ shaping.h my_wctob.h iso88598.h
+
+info-am:
+info: info-am
+dvi-am:
+dvi: dvi-am
+check-am: all-am
+check: check-am
+installcheck-am:
+installcheck: installcheck-am
+all-recursive-am: config.h
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+install-exec-am: install-binPROGRAMS install-binSCRIPTS
+install-exec: install-exec-am
+
+install-data-am: install-pkgdataDATA install-data-local
+install-data: install-data-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+install: install-am
+uninstall-am: uninstall-binPROGRAMS uninstall-binSCRIPTS \
+ uninstall-pkgdataDATA uninstall-local
+uninstall: uninstall-am
+all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(DATA) config.h
+all-redirect: all-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) AM_INSTALL_PROGRAM_FLAGS=-s install
+installdirs:
+ $(mkinstalldirs) $(DESTDIR)$(bindir) $(DESTDIR)$(bindir) \
+ $(DESTDIR)$(pkgdatadir)
+
+
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -rm -f Makefile $(CONFIG_CLEAN_FILES)
+ -rm -f config.cache config.log stamp-h stamp-h[0-9]*
+
+maintainer-clean-generic:
+mostlyclean-am: mostlyclean-hdr mostlyclean-binPROGRAMS \
+ mostlyclean-compile mostlyclean-tags \
+ mostlyclean-generic
+
+mostlyclean: mostlyclean-am
+
+clean-am: clean-hdr clean-binPROGRAMS clean-compile clean-tags \
+ clean-generic mostlyclean-am
+
+clean: clean-am
+
+distclean-am: distclean-hdr distclean-binPROGRAMS distclean-compile \
+ distclean-tags distclean-generic clean-am
+
+distclean: distclean-am
+ -rm -f config.status
+
+maintainer-clean-am: maintainer-clean-hdr maintainer-clean-binPROGRAMS \
+ maintainer-clean-compile maintainer-clean-tags \
+ maintainer-clean-generic distclean-am
+ @echo "This command is intended for maintainers to use;"
+ @echo "it deletes files that may require special tools to rebuild."
+
+maintainer-clean: maintainer-clean-am
+ -rm -f config.status
+
+.PHONY: mostlyclean-hdr distclean-hdr clean-hdr maintainer-clean-hdr \
+mostlyclean-binPROGRAMS distclean-binPROGRAMS clean-binPROGRAMS \
+maintainer-clean-binPROGRAMS uninstall-binPROGRAMS install-binPROGRAMS \
+mostlyclean-compile distclean-compile clean-compile \
+maintainer-clean-compile uninstall-binSCRIPTS install-binSCRIPTS \
+uninstall-pkgdataDATA install-pkgdataDATA tags mostlyclean-tags \
+distclean-tags clean-tags maintainer-clean-tags distdir info-am info \
+dvi-am dvi check check-am installcheck-am installcheck all-recursive-am \
+install-exec-am install-exec install-data-local install-data-am \
+install-data install-am install uninstall-local uninstall-am uninstall \
+all-redirect all-am all installdirs mostlyclean-generic \
+distclean-generic clean-generic maintainer-clean-generic clean \
+mostlyclean distclean maintainer-clean
+
+
+# Hmmm.... what's the correct way to create a directory
+# under pkgdatadir? How can I install the themes using only
+# pkgdata_DATA? Meanwhile, I'll use the following hack:
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)/$(THEME_DIR)
+ @list='$(THEMES)'; for p in $$list; do \
+ if test -f $(srcdir)/$$p; then \
+ echo " $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $(srcdir)/$$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ else if test -f $$p; then \
+ echo " $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p"; \
+ $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p; \
+ fi; fi; \
+ done
+
+uninstall-local:
+ list='$(THEMES)'; for p in $$list; do \
+ rm -f $(DESTDIR)$(pkgdatadir)/$$p; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..fb7058e
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,108 @@
+2004-10-13 VERSION 0.6.3
+
+- The help viewer (accessible via the F1 key) now supports hypertext,
+ making it _much_ easier to navigate the User Manual.
+
+- The User Manual was organized a bit.
+
+- A new command, "Launch external editor", F8, under the "File"
+ menu, allows the user to edit the file using his favorite editor.
+ The cursor position is passed to the external editor, and when
+ using Vim it's also read back. See the manual for details on this
+ "magic".
+
+- C-k now cuts to clipboard (a la emacs).
+
+- Quotes in email messages are ignored when spell-checking (only
+ when syntax == email).
+
+- Filename completeion with TAB is much faster now (stat(2) used only
+ when needed).
+
+- README.themes was update.
+
+- Cantillations (Ta'amei HaMikra) and Arabic vowels were added to the
+ "Insert a Unicode character from the list" submenu.
+
+- Minor improvements.
+
+2004-09-23 VERSION 0.6.1
+
+- Themes (see the "Color schemes" submenu under "Dispaly").
+
+- I've applied Baruch's (@tkos.co.il) patch. This solves a C++
+ compilation problem on some newer systems.
+
+- The encoding submenu was extended with various Cyrillic and Unicode
+ encodings (plus a "Set the default encoding" submenu).
+
+- A new command: C-o (or M-o, in case your Midnight Commander masks
+ it) to jump to the place where the last editing operation took
+ place. It's a bit like Vim's C-o.
+
+- The 'Search' command is a bit friendlier now (it shares the kbd
+ language with the main editing box, and first alpha letter erases
+ contents).
+
+- Syntax highlighting: _text_ and *text* now shows on the console too
+ (it didn't, because the console couldn't show A_UNDERLINE). It's now
+ possible to specify different colors for different levels of quotes
+ in mail messages (see 'edit.email-quoteN' in mc.thm).
+
+- Fixed: you can now do ^Z when the speller is loaded.
+
+- Minor improvements.
+
+2004-08-02 VERSION 0.6.0
+
+- Syntax highlighting for HTML and emails
+
+- Highlighting for *text* and _text_
+
+- Visual/logical cursor movement
+
+- Spell-checking with Hspell now works out-of-the-box.
+ No need to configure anything.
+
+- A new base-directionality algorithm (contextual-*),
+ so Geresh now has a total of five.
+
+- A new sub-menu: "Insert a Unicode character from the list"
+
+- Improved several menus. Direct link to the User Manual from some of them.
+
+- Minor improvements
+
+2004-06-17 VERSION 0.5.0
+
+- Pull-down menu
+
+- Scrollbar
+
+- Better Arabic's harakat support
+
+- Minor improvements
+
+2003-07-16 VERSION 0.4.1
+
+- Fixed a 8-bit locale problem (seems like an ncursesw bug).
+
+2003-07-10 VERSION 0.4.0
+
+- Speller support (using the ispell-a protocol)
+
+- Geresh can now be used as a filter to convert logical to visual
+ (similar to "bidiv", but better).
+
+2003-03-09 VERSION 0.0.3
+
+- event.cc: 8-bit locale kbd handling fixed.
+
+2003-03-04 VERSION 0.0.2
+
+- Minor changes to configure.in. fixed a quoting bug.
+
+2003-02-25 VERSION 0.0.1
+
+- Initial public release.
+
diff --git a/README b/README
new file mode 100644
index 0000000..736525f
--- /dev/null
+++ b/README
@@ -0,0 +1,58 @@
+Geresh is a simple multi-lingual text editor for text terminals.
+
+Installation instructions are detailed in the INSTALL file.
+
+Main features:
+
+* Unicode support
+
+* Works with UTF-8 terminals (and with non-UTF-8 terminals)
+
+* Wide-characters and combining-characters support
+
+* BiDi support (using the FriBiDi library)
+
+* Editing of BiDi formatting codes
+
+* An intelligent algorithm for determining paragraphs direction.
+
+* Arabic shaping, Lam-Alif ligatures, Harakat.
+
+* Alternative display of Hebrew points and maqaf; Hebrew keyboard emulation.
+
+* Can be used as a filter to output a visual representation of the logical
+ input (similar to "bidiv").
+
+* Recognizes (and displays) different line-ends: Unix (LF), Mac (CR),
+ Dos/Windows (CR+LF), Unicode PS, Unicode LS.
+
+* Speller support (supports the ispell-a protocol)
+
+* Undo / redo
+
+* Copy / cut / paste
+
+* Pipes
+
+* Several wrap policies + auto-justify
+
+* Optional iconv support
+
+* Auto-detects UTF-8/16/32 encoded files.
+
+* Can run in UTF-8 or 8-bit locales. Can also run on systems that have
+ neither UTF-8 nor ISO-8859-8 locales.
+
+* Syntax highlighting for HTML, Email, *text* and _text_
+
+* Help system: a comprehensive User Manual (in Hebrew) and a "Describe Key"
+ function (a la emacs).
+
+* Pull-down menu, scrollbar
+
+* Themes (aka "color schemes")
+
+* And more!
+
+Installation instructions are detailed in the INSTALL file.
+
diff --git a/THANKS b/THANKS
new file mode 100644
index 0000000..b6ad8de
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,13 @@
+I'd like to thank the following people:
+
+Beni Cherniavsky
+
+ For providing invaluable feedback and suggestions, and for just
+ using Geresh.
+
+Baruch Birnbaum Siach (from tkos.co.il)
+
+ For providing a patch that solved a C++ compilation problem on
+ some systems.
+
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..2c42949
--- /dev/null
+++ b/TODO
@@ -0,0 +1,58 @@
+HIGH PRIORITY
+=============
+
+- auto-justify is extremely lousy. I must fix that. should support
+ numbered/bulleted items, ">" quotes, two spaces after a dot, etc
+ etc etc.
+
+
+LOWER PRIORITY
+==============
+
+- Emacs:
+ - a kill/clipboard ring [distinct from the clipboard?]
+ - shouldn't M-d, M-backspace cut to clipboard as C-k does?
+ - key-bindings (C-_, C-w, C-y, M-y, etc.)
+
+- bookmarks? (save/restore cursor position). Thankfully, C-o now
+ alleviates the need a bit.
+
+- better Find/Replace.
+
+- add 'bidiv' directionality algo? there's the bigger issue of
+ "paragraphs vs lines" and how to define a paragraph.
+
+- ncursesw has an NSM bug at the rightmost column... (but it seems
+ I can do nothing about it).
+
+- shared clipboard?
+
+- (console only) big cursor: always, or when alt-kbd is on/off.
+
+
+I DON'T THINK I'LL EVER DO THESE
+================================
+
+- C-^ to switch to previous file a la vim? And maybe a "recent
+ files" submenu?
+
+- file browser?
+
+- cygwin/autoconf: check why some of the linking tests don't work on
+ my cygwin.
+
+- ^Z (suspend) when speller is loaded: investigate? (ignoring SIGTSTP
+ in forked child solved this problem, so I can close this item).
+
+- using pipes (opening "!date") when speller is loaded: investigate?
+ (the speller is unloaded when doing pipes, so I can close this item).
+
+- optionally store chars as UCS-2 ?
+
+- speller: fix UTF-8 offsets? (it's not necessary)
+
+- line numbering? --is it useful? for one, it can solve ncursesw's
+ NSM bug.
+
+- "follow symbolic links", etc.
+
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 0000000..c461ff3
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,127 @@
+dnl aclocal.m4 generated automatically by aclocal 1.4-p5
+
+dnl Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+dnl PARTICULAR PURPOSE.
+
+# Do all the work for Automake. This macro actually does too much --
+# some checks are only needed if your package does certain things.
+# But this isn't really a big deal.
+
+# serial 1
+
+dnl Usage:
+dnl AM_INIT_AUTOMAKE(package,version, [no-define])
+
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_REQUIRE([AC_PROG_INSTALL])
+PACKAGE=[$1]
+AC_SUBST(PACKAGE)
+VERSION=[$2]
+AC_SUBST(VERSION)
+dnl test to see if srcdir already configured
+if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then
+ AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+fi
+ifelse([$3],,
+AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package])
+AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package]))
+AC_REQUIRE([AM_SANITY_CHECK])
+AC_REQUIRE([AC_ARG_PROGRAM])
+dnl FIXME This is truly gross.
+missing_dir=`cd $ac_aux_dir && pwd`
+AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir)
+AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir)
+AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir)
+AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir)
+AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir)
+AC_REQUIRE([AC_PROG_MAKE_SET])])
+
+#
+# Check to make sure that the build environment is sane.
+#
+
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Just in case
+sleep 1
+echo timestamp > conftestfile
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ set X `ls -Lt $srcdir/configure conftestfile 2> /dev/null`
+ if test "[$]*" = "X"; then
+ # -L didn't work.
+ set X `ls -t $srcdir/configure conftestfile`
+ fi
+ if test "[$]*" != "X $srcdir/configure conftestfile" \
+ && test "[$]*" != "X conftestfile $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken
+alias in your environment])
+ fi
+
+ test "[$]2" = conftestfile
+ )
+then
+ # Ok.
+ :
+else
+ AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+rm -f conftest*
+AC_MSG_RESULT(yes)])
+
+dnl AM_MISSING_PROG(NAME, PROGRAM, DIRECTORY)
+dnl The program must properly implement --version.
+AC_DEFUN([AM_MISSING_PROG],
+[AC_MSG_CHECKING(for working $2)
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if ($2 --version) < /dev/null > /dev/null 2>&1; then
+ $1=$2
+ AC_MSG_RESULT(found)
+else
+ $1="$3/missing $2"
+ AC_MSG_RESULT(missing)
+fi
+AC_SUBST($1)])
+
+# Like AC_CONFIG_HEADER, but automatically create stamp file.
+
+AC_DEFUN([AM_CONFIG_HEADER],
+[AC_PREREQ([2.12])
+AC_CONFIG_HEADER([$1])
+dnl When config.status generates a header, we must update the stamp-h file.
+dnl This file resides in the same directory as the config header
+dnl that is generated. We must strip everything past the first ":",
+dnl and everything past the last "/".
+AC_OUTPUT_COMMANDS(changequote(<<,>>)dnl
+ifelse(patsubst(<<$1>>, <<[^ ]>>, <<>>), <<>>,
+<<test -z "<<$>>CONFIG_HEADERS" || echo timestamp > patsubst(<<$1>>, <<^\([^:]*/\)?.*>>, <<\1>>)stamp-h<<>>dnl>>,
+<<am_indx=1
+for am_file in <<$1>>; do
+ case " <<$>>CONFIG_HEADERS " in
+ *" <<$>>am_file "*<<)>>
+ echo timestamp > `echo <<$>>am_file | sed -e 's%:.*%%' -e 's%[^/]*$%%'`stamp-h$am_indx
+ ;;
+ esac
+ am_indx=`expr "<<$>>am_indx" + 1`
+done<<>>dnl>>)
+changequote([,]))])
+
diff --git a/basemenu.cc b/basemenu.cc
new file mode 100644
index 0000000..b758803
--- /dev/null
+++ b/basemenu.cc
@@ -0,0 +1,552 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "basemenu.h"
+#include "themes.h"
+
+///////////////////////////// PopupMenu ////////////////////////////////////
+
+PopupMenu::PopupMenu(PopupMenu *aParent, PulldownMenu aMnu)
+{
+ parent = aParent;
+ init(aMnu);
+}
+
+void PopupMenu::init(PulldownMenu aMnu)
+{
+ mnu = aMnu;
+ count = 0;
+ top = 0;
+ current = 0;
+ if (aMnu) {
+ complete_menu(mnu);
+ while (mnu[count].action)
+ count++;
+ }
+}
+
+void PopupMenu::complete_menu(PulldownMenu mnu)
+{
+ for (int i = 0; mnu[i].action; i++) {
+ if (get_primary_target()->get_action_event(mnu[i].action, mnu[i].evt)) {
+ if (!mnu[i].desc)
+ mnu[i].desc = get_primary_target()->get_action_description(mnu[i].action);
+ } else {
+ if (get_secondary_target()) {
+ get_secondary_target()->get_action_event(mnu[i].action, mnu[i].evt);
+ if (!mnu[i].desc)
+ mnu[i].desc = get_secondary_target()->get_action_description(mnu[i].action);
+ }
+ }
+ if (mnu[i].label) {
+ unistring u = u8string(mnu[i].label);
+ if (u.has_char('~')) {
+ mnu[i].shortkey = u[u.index('~') + 1];
+ if (mnu[i].shortkey >= 'A' && mnu[i].shortkey <= 'Z')
+ mnu[i].shortkey -= 'A' - 'a';
+ }
+ }
+ }
+}
+
+INTERACTIVE void PopupMenu::move_previous_item()
+{
+ if (current > 0)
+ while (mnu[--current].action[0] == '-')
+ ;
+ else
+ move_last_item();
+
+ if (current < top)
+ top = current;
+}
+
+INTERACTIVE void PopupMenu::move_next_item()
+{
+ if (current < count - 1)
+ while (mnu[++current].action[0] == '-')
+ ;
+ else
+ move_first_item();
+
+ if (current >= top + window_height() - 2)
+ top = current - window_height() + 3;
+}
+
+INTERACTIVE void PopupMenu::move_first_item()
+{
+ current = top = 0;
+}
+
+INTERACTIVE void PopupMenu::move_last_item()
+{
+ current = count > 1 ? count - 2 : 0;
+ move_next_item(); // update top
+}
+
+void PopupMenu::end_modal(PostResult rslt)
+{
+ post_result = rslt;
+ Widget::end_modal();
+}
+
+INTERACTIVE void PopupMenu::prev_menu()
+{
+ end_modal(mnuPrev);
+}
+
+INTERACTIVE void PopupMenu::next_menu()
+{
+ if (mnu[current].submenu)
+ select();
+ else
+ end_modal(mnuNext);
+}
+
+void PopupMenu::update_ancestors()
+{
+ if (parent) {
+ parent->update_ancestors();
+ parent->update();
+ }
+}
+
+INTERACTIVE void PopupMenu::select()
+{
+ if (mnu[current].command_parameter1 || mnu[current].command_parameter2) {
+ show_hint(""); // clear the dialogline _before_
+ // executing the command (so we won't erase
+ // what the command prints there).
+ do_command(mnu[current].command_parameter1,
+ mnu[current].command_parameter2,
+ mnu[current].command_parameter3);
+ }
+
+ if (!mnu[current].submenu) {
+ // If it's not a submenu then it's easy.
+ if (mnu[current].evt.empty()) {
+ // When the event is not set, it usualy means that
+ // do_command() was executed and no more processing
+ // is required.
+ end_modal(mnuCancel);
+ } else {
+ set_next_event(mnu[current].evt);
+ end_modal(mnuSelect);
+ show_hint("");
+ }
+ return;
+ }
+
+ // No, we need to post a submenu.
+ PopupMenu::PostResult post_result;
+ Event evt;
+ PopupMenu *p = create_popupmenu(this, mnu[current].submenu);
+ post_result = p->post(window_begx() + window_width() - 2,
+ window_begy() + current - top + 1, evt);
+ delete p;
+
+ switch (post_result) {
+ case PopupMenu::mnuSelect:
+ end_modal(mnuSelect);
+ break;
+ case PopupMenu::mnuCancel:
+ end_modal(mnuCancel);
+ break;
+ case PopupMenu::mnuNext:
+ end_modal(mnuNext);
+ break;
+ case PopupMenu::mnuPrev:
+ clear_other_popups();
+ update_ancestors();
+ break;
+ }
+}
+
+INTERACTIVE void PopupMenu::cancel_menu()
+{
+ end_modal(mnuCancel);
+ show_hint("");
+}
+
+bool PopupMenu::handle_event(const Event &evt)
+{
+ if (evt.is_literal()) {
+ for (int i = 0; i < count; i++) {
+ if (mnu[i].shortkey == evt.ch) {
+ current = i;
+ update();
+ select();
+ return true;
+ }
+ }
+ }
+ return Dispatcher::handle_event(evt);
+}
+
+void PopupMenu::draw_frame()
+{
+ int attr = get_attr(MENU_FRAME_ATTR);
+ if (terminal::graphical_boxes) {
+ wborder(wnd, ACS_VLINE|attr, ACS_VLINE|attr, ACS_HLINE|attr, ACS_HLINE|attr,
+ ACS_ULCORNER|attr, ACS_URCORNER|attr, ACS_LLCORNER|attr, ACS_LRCORNER|attr);
+ } else {
+ wborder(wnd, '|'|attr, '|'|attr, '-'|attr, '-'|attr,
+ '+'|attr, '+'|attr, '+'|attr, '+'|attr);
+ }
+
+ if (top != 0)
+ mvwhline(wnd, 0, 2, terminal::graphical_boxes ? ACS_UARROW : '^', 1);
+ if (top + window_height() - 2 < count)
+ mvwhline(wnd, window_height() - 1, 2, terminal::graphical_boxes ? ACS_DARROW : 'v', 1);
+}
+
+int PopupMenu::get_item_optimal_width(int item)
+{
+ if (mnu[item].label)
+ return strlen(mnu[item].label) + 1 + mnu[item].evt.to_string().length() + 2;
+ else
+ return 2;
+}
+
+int PopupMenu::get_optimal_width()
+{
+ int max = 0;
+ for (int i = 0; i < count; i++) {
+ int w = get_item_optimal_width(i);
+ if (w > max)
+ max = w;
+ }
+ return max;
+}
+
+void PopupMenu::update()
+{
+ // Draw the menu.
+
+ int last_visible_item = top + window_height() - 3;
+ if (last_visible_item >= count)
+ last_visible_item = count - 1;
+ int item;
+
+ // Draw the labels
+ for (item = top; item <= last_visible_item; item++) {
+ int y = item - top + 1;
+ wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR));
+ wmove(wnd, y, 1);
+ for (int i = 0; i < window_width() - 2; i++)
+ waddch(wnd, ' ');
+ wmove(wnd, y, 2);
+
+ if (mnu[item].action[0] != '-') {
+ u8string u8key;
+ unistring ulabel = u8string(mnu[item].label);
+
+ int key_ofs = 0;
+ if (mnu[item].shortkey) {
+ key_ofs = ulabel.index('~')+1;
+ unichar key = ulabel[key_ofs];
+ u8key = u8string(unistring(&key, &key + 1));
+ ulabel.erase(ulabel.begin()+key_ofs-1, ulabel.begin()+key_ofs);
+ }
+
+ u8string label_without_tilde(ulabel);
+ draw_string(label_without_tilde.c_str());
+
+ if (mnu[item].shortkey) {
+ wmove(wnd, y, key_ofs+1);
+ wattrset(wnd, (item == current) ? get_attr(MENU_LETTER_SELECTED_ATTR) : get_attr(MENU_LETTER_ATTR));
+ draw_string(u8key.c_str());
+ }
+
+ wattrset(wnd, (item == current) ? get_attr(MENU_INDICATOR_SELECTED_ATTR) : get_attr(MENU_INDICATOR_ATTR));
+ if (mnu[item].state_id) {
+ wmove(wnd, y, 1);
+ int id = mnu[item].state_id;
+ bool checked = get_item_state(id);
+ if (checked || id < 5000) { // Avoid painting a blank (" ")
+ // in order not to affect
+ // the cursor color.
+ draw_string(checked
+ ? (id >= 5000 ? "*" : "+")
+ : (id >= 5000 ? " " : "-"));
+ }
+ }
+ wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR));
+
+ if (mnu[item].submenu) {
+ wmove(wnd, y, window_width()-3);
+ draw_string(">");
+ } else {
+ // Print the hot-key
+ u8string keyname = mnu[item].evt.to_string();
+ wmove(wnd, y, window_width() - keyname.length() - 3);
+ waddch(wnd, ' ');
+ draw_string(keyname.c_str());
+ waddch(wnd, ' ');
+ }
+ }
+ }
+
+ // Draw the frame
+ wattrset(wnd, get_attr(MENU_FRAME_ATTR));
+ draw_frame();
+ for (item = top; item <= last_visible_item; item++) {
+ int y = item - top + 1;
+ if (mnu[item].action[0] == '-') {
+ if (terminal::graphical_boxes) {
+ mvwhline(wnd, y, 0, ACS_LTEE, 1);
+ mvwhline(wnd, y, 1, ACS_HLINE, window_width()-2);
+ mvwhline(wnd, y, window_width()-1, ACS_RTEE, 1);
+ } else {
+ mvwhline(wnd, y, 2, '-', window_width()-4);
+ }
+ }
+ }
+
+ // Place the cursor before the item (useful for monochrome terminals).
+ wmove(wnd, current - top + 1, 1);
+
+ show_hint(mnu[current].desc);
+
+ wnoutrefresh(wnd);
+}
+
+// reposition() - adjusts the window size to fit the screen
+// and the menu items.
+
+void PopupMenu::reposition(int x, int y)
+{
+ int width = get_optimal_width() + 2;
+ int height = count + 2;
+
+ int scr_width, scr_height;
+ getmaxyx(stdscr, scr_height, scr_width);
+
+ if (x + width > scr_width) {
+ x = scr_width - width;
+ if (x < 0)
+ x = 0;
+ if (width > scr_width)
+ width = scr_width;
+ }
+
+ if (y + height > scr_height) {
+ y = scr_height - height;
+ if (y < 0) {
+ y = 0;
+ height = scr_height;
+ }
+ // don't hide the menubar line
+ if (y == 0 && scr_height > 3) {
+ y++;
+ height--;
+ }
+ }
+
+ resize(height, width, y, x);
+}
+
+PopupMenu::PostResult PopupMenu::post(int x, int y, Event &evt)
+{
+ if (!is_valid_window()) // if we haven't yet created it
+ create_window(1, 1);
+ scrollok(wnd, 0); // so we can draw '+' at the bottom right corner.
+ reposition(x, y);
+ set_modal();
+ while (is_modal()) {
+ Event evt;
+ update();
+ doupdate();
+ get_next_event(evt, wnd);
+ handle_event(evt);
+ }
+ //show_hint(""); // I moved it to a better place because I don't
+ // want to erase what do_command might print there.
+ destroy_window();
+ return post_result;
+}
+
+INTERACTIVE void PopupMenu::screen_resize()
+{
+#ifdef KEY_RESIZE
+ set_next_event(Event(KEY_RESIZE));
+ cancel_menu();
+#endif
+}
+
+/////////////////////////////// Menubar ////////////////////////////////////
+
+Menubar::Menubar()
+{
+ create_window(1, 1);
+}
+
+void Menubar::init(MenubarMenu aMnu)
+{
+ mnu = aMnu;
+ count = 0;
+ while (mnu[count].label)
+ count++;
+ current = -1;
+ dirty = true;
+}
+
+void Menubar::set_current(int i)
+{
+ current = i;
+ invalidate_view();
+}
+
+INTERACTIVE void Menubar::select()
+{
+ PopupMenu::PostResult post_result;
+ Event evt;
+
+ PopupMenu *p = create_popupmenu(mnu[current].submenu);
+ post_result = p->post(get_ofs(current), 1, evt);
+ delete p;
+
+ if (post_result == PopupMenu::mnuCancel || post_result == PopupMenu::mnuSelect) {
+ set_current(-1);
+ }
+ refresh_screen();
+
+ switch (post_result) {
+ case PopupMenu::mnuCancel:
+ doupdate();
+ end_modal();
+ break;
+ case PopupMenu::mnuSelect:
+ end_modal();
+ break;
+ case PopupMenu::mnuPrev:
+ prev_menu();
+ update();
+ select();
+ break;
+ case PopupMenu::mnuNext:
+ next_menu();
+ update();
+ select();
+ break;
+ }
+
+}
+
+INTERACTIVE void Menubar::next_menu()
+{
+ if (current >= count - 1)
+ set_current(0);
+ else
+ set_current(current + 1);
+}
+
+INTERACTIVE void Menubar::prev_menu()
+{
+ if (current <= 0)
+ set_current(count - 1);
+ else
+ set_current(current - 1);
+}
+
+int Menubar::get_ofs(int item)
+{
+ int ofs = 1;
+ for (int i = 0; i < item; i++)
+ ofs += strlen(mnu[i].label) + 2;
+ return ofs;
+}
+
+void Menubar::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ invalidate_view();
+}
+
+INTERACTIVE void Menubar::screen_resize()
+{
+#ifdef KEY_RESIZE
+ set_next_event(Event(KEY_RESIZE));
+ end_modal();
+#endif
+}
+
+void Menubar::update()
+{
+ if (!dirty)
+ return;
+
+ werase(wnd);
+ wattrset(wnd, get_attr(MENUBAR_ATTR));
+ wmove(wnd, 0, 0);
+ for (int i = 0; i < window_width(); i++)
+ waddch(wnd, ' ');
+ wmove(wnd, 0, 0);
+
+ for (int i = 0; i < count; i++) {
+ wmove(wnd, 0, get_ofs(i));
+ wattrset(wnd, (current == i) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENUBAR_ATTR));
+ draw_string(current == i ? " " : " ", false);
+ draw_string(mnu[i].label, false);
+ draw_string(current == i ? " " : " ", false);
+ }
+
+ // Place the cursor before the item (useful for monochrome terminals).
+ if (current >= 0)
+ wmove(wnd, 0, get_ofs(current));
+
+ wnoutrefresh(wnd);
+ dirty = false;
+}
+
+bool Menubar::handle_event(const Event &evt)
+{
+ if (evt.is_literal()) {
+ for (int i = 0; i < count; i++) {
+ if (mnu[i].label) {
+ unistring u = u8string(mnu[i].label);
+ unichar shortkey = u[0];
+ if (shortkey >= 'A' && shortkey <= 'Z')
+ shortkey -= 'A' - 'a';
+ if (shortkey == evt.ch) {
+ set_current(i);
+ update();
+ select();
+ return true;
+ }
+ }
+ }
+ }
+ return Dispatcher::handle_event(evt);
+}
+
+void Menubar::exec()
+{
+ set_current(0);
+ set_modal();
+ while (is_modal()) {
+ Event evt;
+ update();
+ doupdate();
+ get_next_event(evt, wnd);
+ handle_event(evt);
+ }
+ set_current(-1);
+ update();
+}
+
diff --git a/basemenu.h b/basemenu.h
new file mode 100644
index 0000000..bd48c4f
--- /dev/null
+++ b/basemenu.h
@@ -0,0 +1,129 @@
+#ifndef BDE_BASEMENU_H
+#define BDE_BASEMENU_H
+
+#include "widget.h"
+
+struct MenuItem {
+ const char *action;
+ const char *label;
+ int state_id;
+ MenuItem *submenu;
+ unsigned long command_parameter1;
+ unsigned long command_parameter2;
+ const char *command_parameter3;
+ const char *desc;
+ Event evt;
+ unichar shortkey;
+};
+
+typedef MenuItem PulldownMenu[];
+
+struct MenubarItem {
+ const char *label;
+ MenuItem *submenu;
+};
+
+typedef MenubarItem MenubarMenu[];
+
+class PopupMenu : public Widget {
+
+public:
+ // The result of the user interaction:
+ enum PostResult { mnuPrev, mnuNext, mnuSelect, mnuCancel };
+
+protected:
+
+ MenuItem *mnu; // The menu.
+ int count; // Number of menu items.
+ int top; // If the menu is too long to fit the screen,
+ // 'top' points to the first visible item.
+ int current; // The highlightd item.
+ PostResult post_result;
+ PopupMenu *parent;
+
+private:
+
+ void draw_frame();
+ int get_optimal_width();
+ int get_item_optimal_width(int item);
+ void update_ancestors();
+ void complete_menu(PulldownMenu mnu);
+ void reposition(int x, int y);
+ void end_modal(PostResult rslt);
+
+protected:
+
+ virtual void show_hint(const char *hint) = 0;
+ virtual void clear_other_popups() = 0;
+ virtual bool get_item_state(int id) = 0;
+ virtual Dispatcher *get_primary_target() = 0;
+ virtual Dispatcher *get_secondary_target() = 0;
+ virtual PopupMenu *create_popupmenu(PopupMenu *aParent, PulldownMenu mnu) = 0;
+ virtual bool handle_event(const Event &evt);
+ virtual void do_command(unsigned long parameter1, unsigned long parameter2,
+ const char *parameter3) = 0;
+
+public:
+
+ HAS_ACTIONS_MAP(PopupMenu, Dispatcher);
+ HAS_BINDINGS_MAP(PopupMenu, Dispatcher);
+
+ PopupMenu(PopupMenu *aParent, PulldownMenu aMnu);
+ void init(PulldownMenu mnu);
+ PostResult post(int x, int y, Event &evt);
+
+ INTERACTIVE void prev_menu();
+ INTERACTIVE void next_menu();
+ INTERACTIVE void select();
+ INTERACTIVE void cancel_menu();
+ INTERACTIVE void screen_resize();
+
+ INTERACTIVE void move_previous_item();
+ INTERACTIVE void move_next_item();
+ INTERACTIVE void move_first_item();
+ INTERACTIVE void move_last_item();
+
+ // from base Widget class:
+ virtual bool is_dirty() const { return true; }
+ virtual void invalidate_view() {}
+ virtual void update();
+};
+
+class Menubar : public Widget {
+
+protected:
+
+ MenubarItem *mnu;
+ int count;
+ int current;
+ bool dirty;
+
+ void set_current(int i);
+ int get_ofs(int item);
+ virtual void refresh_screen() = 0;
+ virtual PopupMenu *create_popupmenu(PulldownMenu mnu) = 0;
+ virtual bool handle_event(const Event &evt);
+
+public:
+
+ HAS_ACTIONS_MAP(Menubar, Dispatcher);
+ HAS_BINDINGS_MAP(Menubar, Dispatcher);
+
+ Menubar();
+ void init(MenubarItem *aMnu);
+ void exec();
+
+ INTERACTIVE void select();
+ INTERACTIVE void next_menu();
+ INTERACTIVE void prev_menu();
+ INTERACTIVE void screen_resize();
+
+ // from base Widget class:
+ virtual bool is_dirty() const { return dirty; }
+ virtual void invalidate_view() { dirty = true; }
+ virtual void update();
+ virtual void resize(int lines, int columns, int y, int x);
+};
+
+#endif
+
diff --git a/bidi.cc b/bidi.cc
new file mode 100644
index 0000000..36ceb8a
--- /dev/null
+++ b/bidi.cc
@@ -0,0 +1,136 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "bidi.h"
+#include "dbg.h"
+
+static ctype_t fribidi_dir(direction_t dir)
+{
+ switch (dir) {
+ case dirLTR: return FRIBIDI_TYPE_LTR;
+ case dirRTL: return FRIBIDI_TYPE_RTL;
+ default: return FRIBIDI_TYPE_ON;
+ }
+}
+
+void BiDi::get_embedding_levels(unichar *str, idx_t len,
+ direction_t dir,
+ level_t *levels,
+ int line_breaks_count,
+ idx_t *line_breaks,
+ bool disable_bidi)
+{
+ if (disable_bidi) {
+ for (idx_t i = len-1; i >= 0; i--)
+ levels[i] = (dir == dirRTL) ? 1 :0;
+ } else {
+ ctype_t base_dir = fribidi_dir(dir);
+ fribidi_log2vis_get_embedding_levels(str, len, &base_dir, levels);
+ }
+
+ // Do rule L1.4 of TR9: wspaces at end of lines.
+ level_t para_embedding_level = (dir == dirRTL ? 1 : 0);
+ int prev_line_break = 0;
+ for (int i = 0; i < line_breaks_count; i++) {
+ int pos = (int)line_breaks[i] - 1;
+ while (pos >= prev_line_break && is_space(str[pos])) {
+ levels[pos] = para_embedding_level;
+ pos--;
+ }
+ prev_line_break = line_breaks[i];
+ }
+}
+
+static void xsimple_log2vis(unichar *str, idx_t len, direction_t dir,
+ unichar *dest)
+{
+ ctype_t base_dir = fribidi_dir(dir);
+ fribidi_log2vis(str, len, &base_dir, dest, NULL, NULL, NULL);
+}
+
+void BiDi::simple_log2vis(unistring &str, direction_t dir, unistring &dest)
+{
+ dest.resize(str.size() + 1); // fribidi_log2vis needs "+1" !!!!!!!!!!!!!
+ xsimple_log2vis(str.begin(), str.size(), dir, dest.begin());
+ dest.resize(str.size());
+}
+
+// determine_base_dir() - determines the base direction of a string.
+// We provide the user with several algorithms. algoUnicode is described
+// in P2,3 of TR9.
+//
+// algoContext's implementation is divided into two: half is here and
+// the other half is in EditBox::calc_contextual_dirs(). See documentation
+// there.
+
+direction_t BiDi::determine_base_dir(unichar *str, idx_t len,
+ diralgo_t dir_algo)
+{
+ ctype_t ctype;
+ switch (dir_algo) {
+ case algoContextRTL:
+ {
+ // - if there's any RTL letter, set direction to LTR;
+ // - else: if there's any LTR letter, set direction to LTR;
+ // - else: set direction to neutral.
+ bool found_LTR = false;
+ for (idx_t i = 0; i < len; i++) {
+ ctype = fribidi_get_type(str[i]);
+ if (FRIBIDI_IS_LETTER(ctype)) {
+ if (FRIBIDI_IS_RTL(ctype))
+ return dirRTL;
+ else
+ found_LTR = true;
+ }
+ }
+ if (found_LTR)
+ return dirLTR;
+ else
+ return dirN;
+ }
+
+ case algoUnicode:
+ case algoContextStrong:
+ {
+ // directionality is determined by the first strong letter.
+ for (idx_t i = 0; i < len; i++) {
+ ctype = fribidi_get_type(str[i]);
+ if (FRIBIDI_IS_LETTER(ctype)) {
+ if (FRIBIDI_IS_RTL(ctype))
+ return dirRTL;
+ else
+ return dirLTR;
+ }
+ }
+ if (dir_algo == algoContextStrong)
+ return dirN;
+ else
+ return dirLTR;
+ }
+
+ case algoForceRTL:
+
+ return dirRTL;
+
+ case algoForceLTR:
+ default:
+
+ return dirLTR;
+ };
+}
+
diff --git a/bidi.h b/bidi.h
new file mode 100644
index 0000000..e1d1a4d
--- /dev/null
+++ b/bidi.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_BIDI_H
+#define BDE_BIDI_H
+
+#include "types.h"
+
+// The purpose of the BiDi class (and the public typdefs) is to make the
+// interface of the actual BiDi engine (FriBiDi) hidden from the user. This
+// BiDi class could be implemented using other engines, e.g. IBM's ICU.
+//
+// Ideally, The BiDi class would be an abstract class from which we derive
+// class FriBiDi, class ICUBiDi etc. The configre script could then tell us
+// which class to use based on what engine the system has.
+//
+// Note that we use very little of FriBiDi. We implement reordering ourselves
+// (see the comments at EditBox::reorder()).
+
+typedef FriBidiCharType ctype_t;
+typedef FriBidiLevel level_t;
+enum direction_t { dirLTR, dirRTL, dirN };
+enum diralgo_t { algoUnicode, algoContextStrong, algoContextRTL, algoForceLTR, algoForceRTL };
+
+#include "univalues.h"
+
+class BiDi {
+
+public:
+
+ static bool mirror_char(unichar *ch) {
+ return fribidi_get_mirror_char(*ch, ch);
+ }
+
+ static bool is_space(unichar ch) {
+ return FRIBIDI_IS_SPACE(fribidi_get_type(ch));
+ }
+
+ static bool is_alnum(unichar ch) {
+ ctype_t ctype = fribidi_get_type(ch);
+ return FRIBIDI_IS_LETTER(ctype) || FRIBIDI_IS_NUMBER(ctype);
+ }
+
+ static bool is_nsm(unichar ch) {
+ return fribidi_get_type(ch) == FRIBIDI_TYPE_NSM;
+ }
+
+ // is_wordch() returns what constitutes a word.
+ static bool is_wordch(unichar ch) {
+ return is_alnum(ch) || is_nsm(ch);
+ }
+
+ static bool is_rtl(unichar ch) {
+ return FRIBIDI_IS_RTL(fribidi_get_type(ch));
+ }
+
+ static bool is_explicit_mark(unichar ch)
+ {
+ return ch == UNI_LRM ||
+ ch == UNI_RLM ||
+ ch == UNI_LRE ||
+ ch == UNI_RLE ||
+ ch == UNI_PDF ||
+ ch == UNI_LRO ||
+ ch == UNI_RLO;
+ }
+
+ static bool is_transparent_formatting_code(unichar ch)
+ {
+ return is_explicit_mark(ch) ||
+ ch == UNI_ZWNJ ||
+ ch == UNI_ZWJ;
+ }
+
+ static bool is_hebrew_nsm(unichar ch)
+ {
+ return (ch >= UNI_HEB_ETNAHTA && ch <= UNI_HEB_UPPER_DOT
+ || ch == UNI_NS_UNDERSCORE)
+ && (ch != UNI_HEB_MAQAF &&
+ ch != UNI_HEB_PASEQ &&
+ ch != UNI_HEB_SOF_PASUQ);
+ }
+
+ static bool is_arabic_nsm(unichar ch)
+ {
+ return (ch >= UNI_ARA_FATHATAN && ch <= UNI_ARA_SUKUN
+ || ch == UNI_ARA_SUPERSCIPT_ALEF);
+ }
+
+ static bool is_cantillation_nsm(unichar ch)
+ {
+ return (ch >= UNI_HEB_ETNAHTA && ch <= UNI_HEB_MASORA_CIRCLE);
+ }
+
+ static void get_embedding_levels(unichar *str, idx_t len,
+ direction_t dir,
+ level_t *levels,
+ int line_breaks_count = 0,
+ idx_t *line_breaks = NULL,
+ bool disable_bidi = false);
+
+ static direction_t determine_base_dir(unichar *str, idx_t len,
+ diralgo_t dir_algo);
+
+ static void simple_log2vis(unistring &str, direction_t dir, unistring &dest);
+};
+
+#endif
+
diff --git a/bindings.cc b/bindings.cc
new file mode 100644
index 0000000..887110e
--- /dev/null
+++ b/bindings.cc
@@ -0,0 +1,510 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "editbox.h"
+#include "editor.h"
+#include "helpbox.h"
+#include "dialogline.h"
+#include "inputline.h"
+#include "speller.h"
+#include "basemenu.h"
+
+#define ESC 27
+
+EditBox::action_entry EditBox::actions_table[] = {
+ ADD_ACTION(EditBox, key_left,
+ N_("Move a character left")),
+ ADD_ACTION(EditBox, key_right,
+ N_("Move a character right")),
+ ADD_ACTION(EditBox, move_previous_line,
+ N_("Move to the previous line")),
+ ADD_ACTION(EditBox, move_next_line,
+ N_("Move to the next line")),
+ ADD_ACTION(EditBox, move_forward_page,
+ N_("Move to the next page")),
+ ADD_ACTION(EditBox, move_backward_page,
+ N_("Move to the previous page")),
+ ADD_ACTION(EditBox, move_forward_char,
+ N_("Move forward a character")),
+ ADD_ACTION(EditBox, move_backward_char,
+ N_("Move back a character")),
+ ADD_ACTION(EditBox, move_beginning_of_line,
+ N_("Move to the beginning of the current line, logical")),
+ ADD_ACTION(EditBox, move_beginning_of_visual_line,
+ N_("Move to the beginning of the current line, visual")),
+ ADD_ACTION(EditBox, key_home,
+ N_("Move to the beginning of the current line")),
+ ADD_ACTION(EditBox, move_end_of_line,
+ N_("Move to the end of the current line")),
+ ADD_ACTION(EditBox, move_last_modification,
+ N_("Jump to the place of the last editing operation")),
+ ADD_ACTION(EditBox, delete_backward_char,
+ N_("Delete the previous character")),
+ ADD_ACTION(EditBox, delete_forward_char,
+ N_("Delete the character the cursor is on")),
+ ADD_ACTION(EditBox, copy,
+ N_("Copy the selected text to clipboard")),
+ ADD_ACTION(EditBox, cut,
+ N_("Copy the selected text to clipboard and delete it from the buffer")),
+ ADD_ACTION(EditBox, paste,
+ N_("Paste the text in the clipboard into the buffer")),
+ ADD_ACTION(EditBox, move_end_of_buffer,
+ N_("Move to the end of the buffer")),
+ ADD_ACTION(EditBox, move_beginning_of_buffer,
+ N_("Move to the beginning of the buffer")),
+ ADD_ACTION(EditBox, move_backward_word,
+ N_("Move to the beginning of the current or previous word")),
+ ADD_ACTION(EditBox, move_forward_word,
+ N_("Move to the end of the current or next word")),
+ ADD_ACTION(EditBox, delete_backward_word,
+ N_("Delete to the beginning of the current or previous word")),
+ ADD_ACTION(EditBox, delete_forward_word,
+ N_("Delete to the end of the current or next word")),
+ ADD_ACTION(EditBox, toggle_dir_algo,
+ N_("Change the algorithm used to determine the base direction of paragraphs")),
+ ADD_ACTION(EditBox, set_dir_algo_unicode,
+ N_("Unicode's TR #9: First strong character determines base dir. Neutral gets LTR.")),
+ ADD_ACTION(EditBox, set_dir_algo_context_strong,
+ N_("Contextual-strong: Unicode's TR #9 + neutral paras now inherit surroundings")),
+ ADD_ACTION(EditBox, set_dir_algo_context_rtl,
+ N_("Contextual-rtl: like Contextual-strong, but if there's any RTL char, para is RTL")),
+ ADD_ACTION(EditBox, set_dir_algo_force_ltr,
+ N_("Force LTR")),
+ ADD_ACTION(EditBox, set_dir_algo_force_rtl,
+ N_("Force RTL")),
+ ADD_ACTION(EditBox, toggle_wrap,
+ N_("Change the way long lines are displayed (whether to wrap or not, and how)")),
+ ADD_ACTION(EditBox, set_wrap_type_at_white_space,
+ N_("Wrap lines, do not break words (just like a word processor)")),
+ ADD_ACTION(EditBox, set_wrap_type_anywhere,
+ N_("Wrap lines, break words")),
+ ADD_ACTION(EditBox, set_wrap_type_off,
+ N_("Don't wrap lines;you'll have to scroll horizontally to view the rest of the line")),
+ ADD_ACTION(EditBox, toggle_alt_kbd,
+ N_("Toogle Hebrew keyboard emulation")),
+ ADD_ACTION(EditBox, set_translate_next_char,
+ N_("Translate next character")),
+ ADD_ACTION(EditBox, justify,
+ N_("Justify the current or next paragraph")),
+ ADD_ACTION(EditBox, cut_end_of_paragraph,
+ N_("Cut [to] end of paragraph")),
+ ADD_ACTION(EditBox, undo,
+ N_("Undo the last change")),
+ ADD_ACTION(EditBox, redo,
+ N_("Redo the last change you canceled")),
+ ADD_ACTION(EditBox, delete_paragraph,
+ N_("Delete the current paragraph")),
+ ADD_ACTION(EditBox, toggle_primary_mark,
+ N_("Start/cancel selection")),
+ ADD_ACTION(EditBox, toggle_auto_justify,
+ N_("Toggle auto-justify")),
+ ADD_ACTION(EditBox, toggle_auto_indent,
+ N_("Toggle auto-indent")),
+ ADD_ACTION(EditBox, toggle_formatting_marks,
+ N_("Toggle display of formatting marks (paragraph ends, explicit BiDi marks, tabs)")),
+ ADD_ACTION(EditBox, toggle_rtl_nsm,
+ N_("Toggle display of Hebrew/Arabic points (off/transliterated/as-is)")),
+ ADD_ACTION(EditBox, set_rtl_nsm_asis,
+ N_("Display Hebrew/Arabic points as-is (for capable terminals only)")),
+ ADD_ACTION(EditBox, set_rtl_nsm_transliterated,
+ N_("Display Hebrew/Arabic points as highlighted ASCII characters")),
+ ADD_ACTION(EditBox, set_rtl_nsm_off,
+ N_("Hide Hebrew/Arabic points")),
+ ADD_ACTION(EditBox, toggle_maqaf,
+ N_("Toggle Hebrew maqaf highlighting and/or enable its ASCII transliteration")),
+ ADD_ACTION(EditBox, set_maqaf_display_transliterated,
+ N_("Display the maqaf as ASCII dash")),
+ ADD_ACTION(EditBox, set_maqaf_display_highlighted,
+ N_("Highlight the maqaf")),
+ ADD_ACTION(EditBox, set_maqaf_display_asis,
+ N_("Display the maqaf as-is (for capable terminals)")),
+ ADD_ACTION(EditBox, toggle_smart_typing,
+ N_("Toggle smart-typing mode: auto replace some plain characters with typographical ones")),
+ ADD_ACTION(EditBox, insert_maqaf,
+ N_("Insert Hebrew maqaf")),
+ ADD_ACTION(EditBox, toggle_read_only,
+ N_("Toggle read-only status of buffer")),
+ ADD_ACTION(EditBox, toggle_eops,
+ N_("Change end-of-paragraphs type")),
+ ADD_ACTION(EditBox, set_eops_unix,
+ N_("Set end-of-paragraphs type to Unix")),
+ ADD_ACTION(EditBox, set_eops_dos,
+ N_("Set end-of-paragraphs type to DOS/Windows")),
+ ADD_ACTION(EditBox, set_eops_mac,
+ N_("Set end-of-paragraphs type to Macintosh")),
+ ADD_ACTION(EditBox, set_eops_unicode,
+ N_("Set end-of-paragraphs type to Unicode PS")),
+ ADD_ACTION(EditBox, toggle_key_for_key_undo,
+ N_("Toggle key-for-key undo (whether to group small editing operations)")),
+ ADD_ACTION(EditBox, toggle_bidi,
+ N_("Turn off/on the BiDi algorithm (useful when editing complicated bi-di texts)")),
+ ADD_ACTION(EditBox, toggle_visual_cursor_movement,
+ N_("Toggle between logical and visual cursor movement")),
+ ADD_ACTION(EditBox, menu_set_syn_hlt_none, N_("Don't do syntax-highlighting")),
+ ADD_ACTION(EditBox, menu_set_syn_hlt_html, N_("Highlight HTML tags")),
+ ADD_ACTION(EditBox, menu_set_syn_hlt_email, N_("Highlight lines starting with '>'")),
+ ADD_ACTION(EditBox, toggle_underline, N_("Whether to highlight *text* and _text_ on your terminal")),
+ END_ACTIONS
+};
+
+binding_entry EditBox::bindings_table[] = {
+ { Event(KEY_LEFT), "key_left" },
+ { Event(KEY_RIGHT), "key_right" },
+ { Event(KEY_UP), "move_previous_line" },
+ { Event(KEY_DOWN), "move_next_line" },
+ { Event(KEY_NPAGE), "move_forward_page" },
+ { Event(KEY_PPAGE), "move_backward_page" },
+ { Event(KEY_HOME), "key_home" },
+ { Event(KEY_END), "move_end_of_line" },
+ { Event(CTRL, 'b'), "move_backward_char" },
+ { Event(CTRL, 'f'), "move_forward_char" },
+ { Event(CTRL, 'p'), "move_previous_line" },
+ { Event(CTRL, 'n'), "move_next_line" },
+ { Event(CTRL, 'a'), "move_beginning_of_line" },
+ { Event(CTRL, 'e'), "move_end_of_line" },
+ { Event(CTRL, 'o'), "move_last_modification" },
+ { Event(ALT, 'o'), "move_last_modification" },
+ { Event(KEY_BACKSPACE), "delete_backward_char" },
+ { Event(KEY_DC), "delete_forward_char" },
+ { Event(CTRL, 'd'), "delete_forward_char" },
+ { Event(ALT, 'h'), "toggle_alt_kbd" },
+ { Event(KEY_F(12)), "toggle_alt_kbd" },
+ { Event(ALT, '>'), "move_end_of_buffer" },
+ { Event(ALT, '<'), "move_beginning_of_buffer" },
+ { Event(ALT, 'b'), "move_backward_word" },
+ { Event(ALT, 'f'), "move_forward_word" },
+ { Event(ALT, 'F'), "toggle_formatting_marks" },
+ { Event(ALT, 'n'), "toggle_rtl_nsm" },
+ { Event(ALT, 'd'), "delete_forward_word" },
+ { Event(ALT, 0, KEY_BACKSPACE), "delete_backward_word" },
+ { Event(ALT, 't'), "toggle_dir_algo" },
+ { Event(ALT, 'w'), "toggle_wrap" },
+ { Event(CTRL, 'q'), "set_translate_next_char" },
+ { Event(CTRL, 'j'), "justify" },
+ { Event(CTRL, 'k'), "cut_end_of_paragraph" },
+ { Event(CTRL, 'r'), "redo" },
+ { Event(CTRL, 'u'), "undo" },
+ { Event(CTRL, 'c'), "copy" },
+ { Event(CTRL, 'x'), "cut" },
+ { Event(CTRL, 'v'), "paste" },
+ { Event(CTRL, 'y'), "delete_paragraph" },
+ { Event(CTRL, '^'), "toggle_primary_mark" },
+ { Event(CTRL, '@'), "toggle_primary_mark" },
+ { Event(KEY_F(11)), "toggle_primary_mark" }, // cygwin
+ { Event(ALT, 'J'), "toggle_auto_justify" },
+ { Event(ALT, 'i'), "toggle_auto_indent" },
+ { Event(ALT, 'k'), "toggle_maqaf" },
+ { Event(ALT, 'q'), "toggle_smart_typing" },
+ { Event(ALT, '-'), "insert_maqaf" },
+ { Event(ALT, 'R'), "toggle_read_only" },
+ { Event(CTRL | ALT, 'e'), "toggle_eops" },
+ { Event(VIRTUAL, 2001), "set_eops_unix" },
+ { Event(VIRTUAL, 2002), "set_eops_dos" },
+ { Event(VIRTUAL, 2003), "set_eops_mac" },
+ { Event(VIRTUAL, 2004), "set_eops_unicode" },
+ { Event(VIRTUAL, 2005), "set_rtl_nsm_off" },
+ { Event(VIRTUAL, 2006), "set_rtl_nsm_asis" },
+ { Event(VIRTUAL, 2007), "set_rtl_nsm_transliterated" },
+ { Event(VIRTUAL, 2008), "set_maqaf_display_transliterated" },
+ { Event(VIRTUAL, 2009), "set_maqaf_display_highlighted" },
+ { Event(VIRTUAL, 2010), "set_maqaf_display_asis" },
+ { Event(VIRTUAL, 2011), "set_wrap_type_at_white_space" },
+ { Event(VIRTUAL, 2012), "set_wrap_type_anywhere" },
+ { Event(VIRTUAL, 2013), "set_wrap_type_off" },
+ { Event(ALT, '1'), "set_dir_algo_unicode" },
+ { Event(ALT, '2'), "set_dir_algo_context_strong" },
+ { Event(ALT, '3'), "set_dir_algo_context_rtl" },
+ { Event(ALT, '4'), "set_dir_algo_force_ltr" },
+ { Event(ALT, '5'), "set_dir_algo_force_rtl" },
+ { Event(VIRTUAL, 2300), "toggle_key_for_key_undo" },
+ { Event(CTRL | ALT, 'b'), "toggle_bidi" },
+ { Event(VIRTUAL, 4000), "menu_set_syn_hlt_none" },
+ { Event(VIRTUAL, 4001), "menu_set_syn_hlt_html" },
+ { Event(VIRTUAL, 4002), "menu_set_syn_hlt_email" },
+ { Event(VIRTUAL, 4010), "toggle_underline" },
+ { Event(ALT, 'v'), "toggle_visual_cursor_movement" },
+ END_BINDINGS
+};
+
+Editor::action_entry Editor::actions_table[] = {
+ ADD_ACTION(Editor, layout_windows, NULL),
+ ADD_ACTION(Editor, load_file,
+ N_("Load file from disk")),
+ ADD_ACTION(Editor, save_file,
+ N_("Save file to disk")),
+ ADD_ACTION(Editor, save_file_as,
+ N_("Save file in another name")),
+ ADD_ACTION(Editor, insert_file,
+ N_("Insert file from disk (or read from pipe) into the buffer")),
+ ADD_ACTION(Editor, change_tab_width,
+ N_("Change the TAB character size")),
+ ADD_ACTION(Editor, change_justification_column,
+ N_("Change the column used to justify paragraphs")),
+ ADD_ACTION(Editor, insert_unicode_char,
+ N_("Insert a specific Unicode character using its known code number")),
+ ADD_ACTION(Editor, go_to_line,
+ N_("Go to a specific line")),
+ ADD_ACTION(Editor, search_forward,
+ N_("Search for a string, starting from the cursor")),
+ ADD_ACTION(Editor, search_forward_next,
+ N_("Search for the next occurrence of the string")),
+ ADD_ACTION(Editor, toggle_cursor_position_report,
+ N_("Toggle continuous display of cursor position in the status line")),
+ ADD_ACTION(Editor, refresh_and_center,
+ N_("Repaint the terminal screen (if it was garbled for some reason)")),
+ ADD_ACTION(Editor, show_character_code,
+ N_("Print the unicode value and UTF-8 sequence of the character the cursor is on")),
+ ADD_ACTION(Editor, show_character_info,
+ N_("Print the corresponding UnicodeData.txt line of the character the cursor is on")),
+ ADD_ACTION(Editor, quit,
+ N_("Quit the editor")),
+ ADD_ACTION(Editor, help,
+ N_("Get help for using this editor")),
+ ADD_ACTION(Editor, describe_key,
+ N_("Gives a short description for a shortcut key")),
+ ADD_ACTION(Editor, change_directory,
+ N_("Change current directory")),
+ ADD_ACTION(Editor, toggle_arabic_shaping,
+ N_("Toggle Arabic shaping and Lam-Alif ligature")),
+ ADD_ACTION(Editor, write_selection_to_file,
+ N_("Write the selected text (or the whole text, if none selected) to a file/pipe")),
+ ADD_ACTION(Editor, change_scroll_step,
+ N_("Change the scroll step (# of lines to scroll up/down when cursor at top/bottom)")),
+ ADD_ACTION(Editor, load_unload_speller,
+ N_("Explicitly load or unload the speller process")),
+ ADD_ACTION(Editor, spell_check_all,
+ N_("Spell check all the document")),
+ ADD_ACTION(Editor, spell_check_forward,
+ N_("Spell check the document, from the cursor onward")),
+ ADD_ACTION(Editor, spell_check_word,
+ N_("Spell check the word on which the cursor stands")),
+ ADD_ACTION(Editor, menu,
+ N_("Activate menu")),
+ ADD_ACTION(Editor, toggle_big_cursor,
+ N_("Toogle big cursor (console only); useful if you're visually impaired")),
+ ADD_ACTION(Editor, toggle_graphical_boxes,
+ N_("Use graphical characters for the menus and scrollbar")),
+ ADD_ACTION(Editor, external_edit_prompt,
+ N_("Edit this file with an external editor, prompt for editor command")),
+ ADD_ACTION(Editor, external_edit_no_prompt,
+ N_("Edit this file with an external editor (using previously entered command)")),
+ ADD_ACTION(Editor, menu_set_scrollbar_none, NULL),
+ ADD_ACTION(Editor, menu_set_scrollbar_left, NULL),
+ ADD_ACTION(Editor, menu_set_scrollbar_right, NULL),
+ ADD_ACTION(Editor, toggle_syntax_auto_detection, N_("Whether to try to detect HTML files and email messages")),
+ ADD_ACTION(Editor, set_default_theme, NULL),
+ END_ACTIONS
+};
+
+binding_entry Editor::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "layout_windows" },
+#endif
+ { Event(KEY_F(1)), "help" },
+ { Event(KEY_F(2)), "save_file" },
+ { Event(CTRL, 's'), "save_file_as" },
+ { Event(CTRL, 'w'), "write_selection_to_file" },
+ { Event(KEY_F(3)), "load_file" },
+ { Event(KEY_F(7)), "search_forward" },
+ { Event(KEY_F(17)), "search_forward_next" },
+ { Event(ALT, 'r'), "insert_file" },
+ { Event(CTRL | ALT, 'c'), "change_directory" },
+ { Event(ALT, 'x'), "quit" },
+ { Event(ALT, 'X'), "quit" },
+ { Event(ALT, 'c'), "toggle_cursor_position_report" },
+ { Event(CTRL | ALT, 't'), "change_tab_width" },
+ { Event(CTRL | ALT, 'j'), "change_justification_column" },
+ { Event(CTRL | ALT, 'v'), "insert_unicode_char"},
+ { Event(CTRL | ALT, 'u'), "show_character_code"},
+ { Event(CTRL | ALT, 's'), "change_scroll_step"},
+ { Event(ALT, 'a'), "toggle_arabic_shaping"},
+ { Event(ALT, '\t'), "show_character_info"},
+ { Event(CTRL, 'g'), "go_to_line"},
+ { Event(CTRL, 'l'), "refresh_and_center"},
+ { Event(KEY_F(4)), "describe_key"},
+ { Event(KEY_F(5)), "spell_check_all" },
+ { Event(KEY_F(6)), "spell_check_forward" },
+ { Event(ALT, '$'), "spell_check_word" },
+ { Event(ALT, 'S'), "load_unload_speller" },
+ { Event(KEY_F(9)), "menu" },
+ { Event(KEY_F(10)), "menu" },
+ { Event(ALT, 0, KEY_F(8)), "external_edit_prompt" },
+ { Event(KEY_F(8)), "external_edit_no_prompt" },
+ { Event(VIRTUAL, 3000), "toggle_big_cursor" },
+ { Event(VIRTUAL, 1100), "toggle_graphical_boxes" },
+ { Event(VIRTUAL, 1200), "menu_set_scrollbar_none" },
+ { Event(VIRTUAL, 1201), "menu_set_scrollbar_left" },
+ { Event(VIRTUAL, 1202), "menu_set_scrollbar_right" },
+ { Event(VIRTUAL, 1300), "toggle_syntax_auto_detection" },
+ { Event(VIRTUAL, 1400), "set_default_theme" },
+ END_BINDINGS
+};
+
+DialogLine::action_entry DialogLine::actions_table[] = {
+ ADD_ACTION(DialogLine, layout_windows, NULL),
+ ADD_ACTION(DialogLine, cancel_modal, NULL),
+ ADD_ACTION(DialogLine, refresh, NULL),
+ END_ACTIONS
+};
+
+binding_entry DialogLine::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "layout_windows" },
+#endif
+ { Event(CTRL, 'g'), "cancel_modal" },
+ { Event(CTRL, 'c'), "cancel_modal" },
+ { Event(0, ESC), "cancel_modal" },
+ { Event(CTRL, 'l'), "refresh" },
+ END_BINDINGS
+};
+
+InputLine::action_entry InputLine::actions_table[] = {
+ ADD_ACTION(InputLine, previous_completion, NULL),
+ ADD_ACTION(InputLine, next_completion, NULL),
+ ADD_ACTION(InputLine, end_modal, NULL),
+ ADD_ACTION(InputLine, previous_history, NULL),
+ ADD_ACTION(InputLine, next_history, NULL),
+ END_ACTIONS
+};
+
+binding_entry InputLine::bindings_table[] = {
+ { Event(0, '\r'), "end_modal" },
+ { Event(0, '\t'), "next_completion" },
+ { Event(ALT, '\t'), "previous_completion" },
+ { Event(CTRL, 'p'), "previous_history" },
+ { Event(CTRL, 'n'), "next_history" },
+ { Event(KEY_UP), "previous_history" },
+ { Event(KEY_DOWN), "next_history" },
+ END_BINDINGS
+};
+
+HelpBox::action_entry HelpBox::actions_table[] = {
+ ADD_ACTION(HelpBox, end_modal, NULL),
+ ADD_ACTION(HelpBox, layout_windows, NULL),
+ ADD_ACTION(HelpBox, refresh_and_center, NULL),
+ ADD_ACTION(HelpBox, move_to_toc, NULL),
+ ADD_ACTION(HelpBox, pop_position, NULL),
+ END_ACTIONS
+};
+
+binding_entry HelpBox::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "layout_windows" },
+#endif
+ { Event(CTRL, 'l'), "refresh_and_center"},
+ { Event(KEY_F(1)), "end_modal" },
+ { Event(ALT, 'x'), "end_modal" },
+ { Event(ALT, 'X'), "end_modal" },
+ { Event(0, ESC), "end_modal" },
+ { Event(ALT, 'b'), "pop_position" },
+ { Event(ALT, 't'), "move_to_toc" },
+ END_BINDINGS
+};
+
+SpellerWnd::action_entry SpellerWnd::actions_table[] = {
+ ADD_ACTION(SpellerWnd, layout_windows, NULL),
+ ADD_ACTION(SpellerWnd, ignore_word, NULL),
+ ADD_ACTION(SpellerWnd, abort_spelling, NULL),
+ ADD_ACTION(SpellerWnd, abort_spelling_restore_cursor, NULL),
+ ADD_ACTION(SpellerWnd, add_to_dict, NULL),
+ ADD_ACTION(SpellerWnd, edit_replacement, NULL),
+ ADD_ACTION(SpellerWnd, set_global_decision, NULL),
+ ADD_ACTION(SpellerWnd, refresh, NULL),
+ END_ACTIONS
+};
+
+binding_entry SpellerWnd::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "layout_windows" },
+#endif
+ { Event(CTRL, 'g'), "abort_spelling" },
+ { Event(CTRL, 'c'), "abort_spelling" },
+ { Event(0, ESC), "abort_spelling" },
+ { Event(ALT, 'x'), "abort_spelling_restore_cursor" },
+ { Event(0, 'q'), "abort_spelling_restore_cursor" },
+ { Event(0, 'x'), "abort_spelling_restore_cursor" },
+ { Event(CTRL, 'l'), "refresh" },
+ { Event(0, ' '), "ignore_word" },
+ { Event(0, 'a'), "add_to_dict" },
+ { Event(0, 'r'), "edit_replacement" },
+ { Event(0, 'g'), "set_global_decision" },
+ END_BINDINGS
+};
+
+PopupMenu::action_entry PopupMenu::actions_table[] = {
+ ADD_ACTION(PopupMenu, cancel_menu, NULL),
+ ADD_ACTION(PopupMenu, prev_menu, NULL),
+ ADD_ACTION(PopupMenu, next_menu, NULL),
+ ADD_ACTION(PopupMenu, select, NULL),
+ ADD_ACTION(PopupMenu, move_previous_item, NULL),
+ ADD_ACTION(PopupMenu, move_next_item, NULL),
+ ADD_ACTION(PopupMenu, move_first_item, NULL),
+ ADD_ACTION(PopupMenu, move_last_item, NULL),
+ ADD_ACTION(PopupMenu, screen_resize, NULL),
+ END_ACTIONS
+};
+
+binding_entry PopupMenu::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "screen_resize" },
+#endif
+ { Event(KEY_F(9)), "cancel_menu" },
+ { Event(KEY_F(10)), "cancel_menu" },
+ { Event(CTRL, 'c'), "cancel_menu" },
+ { Event(CTRL, 'g'), "cancel_menu" },
+ { Event(0, ESC), "cancel_menu" },
+ { Event(KEY_LEFT), "prev_menu" },
+ { Event(KEY_RIGHT), "next_menu" },
+ { Event(0, '\r'), "select" },
+ { Event(KEY_UP), "move_previous_item" },
+ { Event(KEY_DOWN), "move_next_item" },
+ { Event(KEY_HOME), "move_first_item" },
+ { Event(KEY_END), "move_last_item" },
+ { Event(KEY_PPAGE), "move_first_item" },
+ { Event(KEY_NPAGE), "move_last_item" },
+ { Event(CTRL, 'p'), "move_previous_item" },
+ { Event(CTRL, 'n'), "move_next_item" },
+ END_BINDINGS
+};
+
+Menubar::action_entry Menubar::actions_table[] = {
+ ADD_ACTION(Menubar, select, NULL),
+ ADD_ACTION(Menubar, next_menu, NULL),
+ ADD_ACTION(Menubar, prev_menu, NULL),
+ ADD_ACTION(Menubar, end_modal, NULL),
+ ADD_ACTION(Menubar, screen_resize, NULL),
+ END_ACTIONS
+};
+
+binding_entry Menubar::bindings_table[] = {
+#ifdef KEY_RESIZE
+ { Event(KEY_RESIZE), "screen_resize" },
+#endif
+ { Event(KEY_F(9)), "end_modal" },
+ { Event(KEY_F(10)), "end_modal" },
+ { Event(CTRL, 'c'), "end_modal" },
+ { Event(CTRL, 'g'), "end_modal" },
+ { Event(0, ESC), "end_modal" },
+ { Event(KEY_LEFT), "prev_menu" },
+ { Event(KEY_RIGHT), "next_menu" },
+ { Event(KEY_DOWN), "select" },
+ { Event(0, '\r'), "select" },
+ END_BINDINGS
+};
+
+
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..1bd4752
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,75 @@
+#ifndef BDE_CONFIG_H
+#define BDE_CONFIG_H
+
+#undef PACKAGE
+#undef VERSION
+
+// enable debugging messages?
+#undef DEBUG
+
+#undef HAVE_NCURSESW_NCURSES_H
+
+#undef HAVE_NCURSES_H
+
+// are we using wide-curses?
+#undef HAVE_WIDE_CURSES
+
+// do we have nl_langinfo(), CODESET?
+#undef HAVE_LANGINFO_CODESET
+
+#undef HAVE_LOCALE_H
+
+#undef HAVE_SETLOCALE
+
+// use GNU's gettext?
+#undef USE_GETTEXT
+
+// does our curses support colors?
+#undef HAVE_COLOR
+
+// do we have the use_default_colors() non-standard function?
+#undef HAVE_USE_DEFAULT_COLORS
+
+// can we change cursor visibility with curs_set()?
+#undef HAVE_CURS_SET
+
+// wctob() is for 8-bit locales
+#undef HAVE_WCTOB
+
+// btowc() is for 8-bit locales
+#undef HAVE_BTOWC
+
+// can we parse long command-line arguments?
+#undef HAVE_GETOPT_LONG
+
+// use iconv for charset conversion?
+#undef USE_ICONV
+
+// iconv()'s declaration has "const" for the second argument?
+#undef ICONV_CONST
+
+// what is iconv's name of our internal encoding?
+#undef INTERNAL_ENCODING
+
+// default file encoding
+#undef DEFAULT_FILE_ENCODING
+
+// DIR and dirent
+#undef HAVE_DIRENT_H
+
+#undef HAVE_SYS_NDIR_H
+
+#undef HAVE_SYS_DIR_H
+
+#undef HAVE_NDIR_H
+
+#undef mode_t
+
+#undef RETSIGTYPE
+
+#undef HAVE_VSNPRINTF
+
+#undef HAVE_VASPRINTF
+
+#endif
+
diff --git a/configure b/configure
new file mode 100755
index 0000000..24e067a
--- /dev/null
+++ b/configure
@@ -0,0 +1,3518 @@
+#! /bin/sh
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.13
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+ac_help="$ac_help
+ --enable-debug Enable debugging support"
+ac_help="$ac_help
+ --with-curses=DIR Where [n]curses[w] is installed "
+ac_help="$ac_help
+ --with-iconv=DIR Where iconv is installed "
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+# Maximum number of lines to put in a shell here document.
+ac_max_here_lines=12
+
+ac_prev=
+for ac_option
+do
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ case "$ac_option" in
+ -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) ac_optarg= ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case "$ac_option" in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir="$ac_optarg" ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build="$ac_optarg" ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file="$ac_optarg" ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir="$ac_optarg" ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ eval "enable_${ac_feature}=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix="$ac_optarg" ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he)
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+ --cache-file=FILE cache test results in FILE
+ --help print this message
+ --no-create do not create output files
+ --quiet, --silent do not print \`checking...' messages
+ --version print the version of autoconf that created configure
+Directory and file names:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [same as prefix]
+ --bindir=DIR user executables in DIR [EPREFIX/bin]
+ --sbindir=DIR system admin executables in DIR [EPREFIX/sbin]
+ --libexecdir=DIR program executables in DIR [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data in DIR
+ [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data in DIR
+ [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var]
+ --libdir=DIR object code libraries in DIR [EPREFIX/lib]
+ --includedir=DIR C header files in DIR [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include]
+ --infodir=DIR info documentation in DIR [PREFIX/info]
+ --mandir=DIR man documentation in DIR [PREFIX/man]
+ --srcdir=DIR find the sources in DIR [configure dir or ..]
+ --program-prefix=PREFIX prepend PREFIX to installed program names
+ --program-suffix=SUFFIX append SUFFIX to installed program names
+ --program-transform-name=PROGRAM
+ run sed PROGRAM on installed program names
+EOF
+ cat << EOF
+Host type:
+ --build=BUILD configure for building on BUILD [BUILD=HOST]
+ --host=HOST configure for HOST [guessed]
+ --target=TARGET configure for TARGET [TARGET=HOST]
+Features and packages:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --x-includes=DIR X include files are in DIR
+ --x-libraries=DIR X library files are in DIR
+EOF
+ if test -n "$ac_help"; then
+ echo "--enable and --with options recognized:$ac_help"
+ fi
+ exit 0 ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host="$ac_optarg" ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir="$ac_optarg" ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir="$ac_optarg" ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir="$ac_optarg" ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir="$ac_optarg" ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir="$ac_optarg" ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir="$ac_optarg" ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir="$ac_optarg" ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix="$ac_optarg" ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix="$ac_optarg" ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix="$ac_optarg" ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name="$ac_optarg" ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir="$ac_optarg" ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir="$ac_optarg" ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site="$ac_optarg" ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir="$ac_optarg" ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir="$ac_optarg" ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target="$ac_optarg" ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers)
+ echo "configure generated by autoconf version 2.13"
+ exit 0 ;;
+
+ -with-* | --with-*)
+ ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_${ac_package}='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ eval "with_${ac_package}=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes="$ac_optarg" ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries="$ac_optarg" ;;
+
+ -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+ ;;
+
+ *)
+ if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+ echo "configure: warning: $ac_option: invalid host type" 1>&2
+ fi
+ if test "x$nonopt" != xNONE; then
+ { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+ fi
+ nonopt="$ac_option"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+ exec 6>/dev/null
+else
+ exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+ case "$ac_arg" in
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c) ;;
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+ ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+ esac
+done
+
+# NLS nuisances.
+# Only set these to C if already set. These must not be set unconditionally
+# because not all systems understand e.g. LANG=C (notably SCO).
+# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'!
+# Non-C LC_CTYPE values break the ctype check.
+if test "${LANG+set}" = set; then LANG=C; export LANG; fi
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
+if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=main.cc
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_prog=$0
+ ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+ test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+ else
+ { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+ fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ echo "loading site script $ac_site_file"
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ echo "loading cache $cache_file"
+ . $cache_file
+else
+ echo "creating cache $cache_file"
+ > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ac_exeext=
+ac_objext=o
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+ # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+ if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+ ac_n= ac_c='
+' ac_t=' '
+ else
+ ac_n=-n ac_c= ac_t=
+ fi
+else
+ ac_n= ac_c='\c' ac_t=
+fi
+
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; }
+fi
+ac_config_guess=$ac_aux_dir/config.guess
+ac_config_sub=$ac_aux_dir/config.sub
+ac_configure=$ac_aux_dir/configure # This should be Cygnus configure.
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
+echo "configure:562: checking for a BSD compatible install" >&5
+if test -z "$INSTALL"; then
+if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":"
+ for ac_dir in $PATH; do
+ # Account for people who put trailing slashes in PATH elements.
+ case "$ac_dir/" in
+ /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ if test -f $ac_dir/$ac_prog; then
+ if test $ac_prog = install &&
+ grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ else
+ ac_cv_path_install="$ac_dir/$ac_prog -c"
+ break 2
+ fi
+ fi
+ done
+ ;;
+ esac
+ done
+ IFS="$ac_save_IFS"
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL="$ac_cv_path_install"
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL="$ac_install_sh"
+ fi
+fi
+echo "$ac_t""$INSTALL" 1>&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+echo $ac_n "checking whether build environment is sane""... $ac_c" 1>&6
+echo "configure:615: checking whether build environment is sane" >&5
+# Just in case
+sleep 1
+echo timestamp > conftestfile
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ set X `ls -Lt $srcdir/configure conftestfile 2> /dev/null`
+ if test "$*" = "X"; then
+ # -L didn't work.
+ set X `ls -t $srcdir/configure conftestfile`
+ fi
+ if test "$*" != "X $srcdir/configure conftestfile" \
+ && test "$*" != "X conftestfile $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ { echo "configure: error: ls -t appears to fail. Make sure there is not a broken
+alias in your environment" 1>&2; exit 1; }
+ fi
+
+ test "$2" = conftestfile
+ )
+then
+ # Ok.
+ :
+else
+ { echo "configure: error: newly created file is older than distributed files!
+Check your system clock" 1>&2; exit 1; }
+fi
+rm -f conftest*
+echo "$ac_t""yes" 1>&6
+if test "$program_transform_name" = s,x,x,; then
+ program_transform_name=
+else
+ # Double any \ or $. echo might interpret backslashes.
+ cat <<\EOF_SED > conftestsed
+s,\\,\\\\,g; s,\$,$$,g
+EOF_SED
+ program_transform_name="`echo $program_transform_name|sed -f conftestsed`"
+ rm -f conftestsed
+fi
+test "$program_prefix" != NONE &&
+ program_transform_name="s,^,${program_prefix},; $program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+ program_transform_name="s,\$\$,${program_suffix},; $program_transform_name"
+
+# sed with no file args requires a program.
+test "$program_transform_name" = "" && program_transform_name="s,x,x,"
+
+echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6
+echo "configure:672: checking whether ${MAKE-make} sets \${MAKE}" >&5
+set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftestmake <<\EOF
+all:
+ @echo 'ac_maketemp="${MAKE}"'
+EOF
+# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
+eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=`
+if test -n "$ac_maketemp"; then
+ eval ac_cv_prog_make_${ac_make}_set=yes
+else
+ eval ac_cv_prog_make_${ac_make}_set=no
+fi
+rm -f conftestmake
+fi
+if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SET_MAKE=
+else
+ echo "$ac_t""no" 1>&6
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+
+PACKAGE=geresh
+
+VERSION=0.6.3
+
+if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then
+ { echo "configure: error: source directory already configured; run "make distclean" there first" 1>&2; exit 1; }
+fi
+cat >> confdefs.h <<EOF
+#define PACKAGE "$PACKAGE"
+EOF
+
+cat >> confdefs.h <<EOF
+#define VERSION "$VERSION"
+EOF
+
+
+
+missing_dir=`cd $ac_aux_dir && pwd`
+echo $ac_n "checking for working aclocal""... $ac_c" 1>&6
+echo "configure:718: checking for working aclocal" >&5
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if (aclocal --version) < /dev/null > /dev/null 2>&1; then
+ ACLOCAL=aclocal
+ echo "$ac_t""found" 1>&6
+else
+ ACLOCAL="$missing_dir/missing aclocal"
+ echo "$ac_t""missing" 1>&6
+fi
+
+echo $ac_n "checking for working autoconf""... $ac_c" 1>&6
+echo "configure:731: checking for working autoconf" >&5
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if (autoconf --version) < /dev/null > /dev/null 2>&1; then
+ AUTOCONF=autoconf
+ echo "$ac_t""found" 1>&6
+else
+ AUTOCONF="$missing_dir/missing autoconf"
+ echo "$ac_t""missing" 1>&6
+fi
+
+echo $ac_n "checking for working automake""... $ac_c" 1>&6
+echo "configure:744: checking for working automake" >&5
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if (automake --version) < /dev/null > /dev/null 2>&1; then
+ AUTOMAKE=automake
+ echo "$ac_t""found" 1>&6
+else
+ AUTOMAKE="$missing_dir/missing automake"
+ echo "$ac_t""missing" 1>&6
+fi
+
+echo $ac_n "checking for working autoheader""... $ac_c" 1>&6
+echo "configure:757: checking for working autoheader" >&5
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if (autoheader --version) < /dev/null > /dev/null 2>&1; then
+ AUTOHEADER=autoheader
+ echo "$ac_t""found" 1>&6
+else
+ AUTOHEADER="$missing_dir/missing autoheader"
+ echo "$ac_t""missing" 1>&6
+fi
+
+echo $ac_n "checking for working makeinfo""... $ac_c" 1>&6
+echo "configure:770: checking for working makeinfo" >&5
+# Run test in a subshell; some versions of sh will print an error if
+# an executable is not found, even if stderr is redirected.
+# Redirect stdin to placate older versions of autoconf. Sigh.
+if (makeinfo --version) < /dev/null > /dev/null 2>&1; then
+ MAKEINFO=makeinfo
+ echo "$ac_t""found" 1>&6
+else
+ MAKEINFO="$missing_dir/missing makeinfo"
+ echo "$ac_t""missing" 1>&6
+fi
+
+
+
+
+
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:790: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CC="gcc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:820: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_prog_rejected=no
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# -gt 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ set dummy "$ac_dir/$ac_word" "$@"
+ shift
+ ac_cv_prog_CC="$@"
+ fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -z "$CC"; then
+ case "`uname -s`" in
+ *win32* | *WIN32*)
+ # Extract the first word of "cl", so it can be a program name with args.
+set dummy cl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:871: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CC="cl"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+ esac
+ fi
+ test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:903: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 914 "configure"
+#include "confdefs.h"
+
+main(){return(0);}
+EOF
+if { (eval echo configure:919: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ ac_cv_prog_cc_works=yes
+ # If we can't run a trivial program, we are probably using a cross compiler.
+ if (./conftest; exit) 2>/dev/null; then
+ ac_cv_prog_cc_cross=no
+ else
+ ac_cv_prog_cc_cross=yes
+ fi
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ ac_cv_prog_cc_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cc_works" 1>&6
+if test $ac_cv_prog_cc_works = no; then
+ { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:945: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+echo "configure:950: checking whether we are using GNU C" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.c <<EOF
+#ifdef __GNUC__
+ yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:959: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+ ac_cv_prog_gcc=yes
+else
+ ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+
+if test $ac_cv_prog_gcc = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+
+ac_test_CFLAGS="${CFLAGS+set}"
+ac_save_CFLAGS="$CFLAGS"
+CFLAGS=
+echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+echo "configure:978: checking whether ${CC-cc} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+ ac_cv_prog_cc_g=yes
+else
+ ac_cv_prog_cc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cc_g" 1>&6
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS="$ac_save_CFLAGS"
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+
+echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+echo "configure:1010: checking how to run the C preprocessor" >&5
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ # This must be in double quotes, not single quotes, because CPP may get
+ # substituted into the Makefile and "${CC-cc}" will confuse make.
+ CPP="${CC-cc} -E"
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp.
+ cat > conftest.$ac_ext <<EOF
+#line 1025 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1031: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP="${CC-cc} -E -traditional-cpp"
+ cat > conftest.$ac_ext <<EOF
+#line 1042 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1048: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP="${CC-cc} -nologo -E"
+ cat > conftest.$ac_ext <<EOF
+#line 1059 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1065: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+ ac_cv_prog_CPP="$CPP"
+fi
+ CPP="$ac_cv_prog_CPP"
+else
+ ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
+for ac_prog in $CCC c++ g++ gcc CC cxx cc++ cl
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:1094: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CXX'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CXX"; then
+ ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CXX="$ac_prog"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CXX="$ac_cv_prog_CXX"
+if test -n "$CXX"; then
+ echo "$ac_t""$CXX" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$CXX" && break
+done
+test -n "$CXX" || CXX="gcc"
+
+
+echo $ac_n "checking whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:1126: checking whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) works" >&5
+
+ac_ext=C
+# CXXFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='${CXX-g++} -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cxx_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 1137 "configure"
+#include "confdefs.h"
+
+int main(){return(0);}
+EOF
+if { (eval echo configure:1142: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ ac_cv_prog_cxx_works=yes
+ # If we can't run a trivial program, we are probably using a cross compiler.
+ if (./conftest; exit) 2>/dev/null; then
+ ac_cv_prog_cxx_cross=no
+ else
+ ac_cv_prog_cxx_cross=yes
+ fi
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ ac_cv_prog_cxx_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cxx_works" 1>&6
+if test $ac_cv_prog_cxx_works = no; then
+ { echo "configure: error: installation or configuration problem: C++ compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:1168: checking whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cxx_cross" 1>&6
+cross_compiling=$ac_cv_prog_cxx_cross
+
+echo $ac_n "checking whether we are using GNU C++""... $ac_c" 1>&6
+echo "configure:1173: checking whether we are using GNU C++" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gxx'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.C <<EOF
+#ifdef __GNUC__
+ yes;
+#endif
+EOF
+if { ac_try='${CXX-g++} -E conftest.C'; { (eval echo configure:1182: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+ ac_cv_prog_gxx=yes
+else
+ ac_cv_prog_gxx=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gxx" 1>&6
+
+if test $ac_cv_prog_gxx = yes; then
+ GXX=yes
+else
+ GXX=
+fi
+
+ac_test_CXXFLAGS="${CXXFLAGS+set}"
+ac_save_CXXFLAGS="$CXXFLAGS"
+CXXFLAGS=
+echo $ac_n "checking whether ${CXX-g++} accepts -g""... $ac_c" 1>&6
+echo "configure:1201: checking whether ${CXX-g++} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cxx_g'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ echo 'void f(){}' > conftest.cc
+if test -z "`${CXX-g++} -g -c conftest.cc 2>&1`"; then
+ ac_cv_prog_cxx_g=yes
+else
+ ac_cv_prog_cxx_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cxx_g" 1>&6
+if test "$ac_test_CXXFLAGS" = set; then
+ CXXFLAGS="$ac_save_CXXFLAGS"
+elif test $ac_cv_prog_cxx_g = yes; then
+ if test "$GXX" = yes; then
+ CXXFLAGS="-g -O2"
+ else
+ CXXFLAGS="-g"
+ fi
+else
+ if test "$GXX" = yes; then
+ CXXFLAGS="-O2"
+ else
+ CXXFLAGS=
+ fi
+fi
+
+for ac_declaration in \
+ ''\
+ '#include <stdlib.h>' \
+ 'extern "C" void std::exit (int) throw (); using std::exit;' \
+ 'extern "C" void std::exit (int); using std::exit;' \
+ 'extern "C" void exit (int) throw ();' \
+ 'extern "C" void exit (int);' \
+ 'void exit (int);'
+do
+ cat > conftest.$ac_ext <<EOF
+#line 1242 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+$ac_declaration
+int main() {
+exit (42);
+; return 0; }
+EOF
+if { (eval echo configure:1250: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ :
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ continue
+fi
+rm -f conftest*
+ cat > conftest.$ac_ext <<EOF
+#line 1260 "configure"
+#include "confdefs.h"
+$ac_declaration
+int main() {
+exit (42);
+; return 0; }
+EOF
+if { (eval echo configure:1267: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+if test -n "$ac_declaration"; then
+ echo '#ifdef __cplusplus' >>confdefs.h
+ echo $ac_declaration >>confdefs.h
+ echo '#endif' >>confdefs.h
+fi
+
+
+echo $ac_n "checking how to run the C++ preprocessor""... $ac_c" 1>&6
+echo "configure:1284: checking how to run the C++ preprocessor" >&5
+if test -z "$CXXCPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CXXCPP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_ext=C
+# CXXFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='${CXX-g++} -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cxx_cross
+ CXXCPP="${CXX-g++} -E"
+ cat > conftest.$ac_ext <<EOF
+#line 1297 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1302: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CXXCPP=/lib/cpp
+fi
+rm -f conftest*
+ ac_cv_prog_CXXCPP="$CXXCPP"
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+fi
+fi
+CXXCPP="$ac_cv_prog_CXXCPP"
+echo "$ac_t""$CXXCPP" 1>&6
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
+echo "configure:1338: checking for a BSD compatible install" >&5
+if test -z "$INSTALL"; then
+if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":"
+ for ac_dir in $PATH; do
+ # Account for people who put trailing slashes in PATH elements.
+ case "$ac_dir/" in
+ /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ if test -f $ac_dir/$ac_prog; then
+ if test $ac_prog = install &&
+ grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ else
+ ac_cv_path_install="$ac_dir/$ac_prog -c"
+ break 2
+ fi
+ fi
+ done
+ ;;
+ esac
+ done
+ IFS="$ac_save_IFS"
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL="$ac_cv_path_install"
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL="$ac_install_sh"
+ fi
+fi
+echo "$ac_t""$INSTALL" 1>&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+
+ac_ext=C
+# CXXFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='${CXX-g++} -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cxx_cross
+
+
+if test "$program_transform_name" = s,x,x,; then
+ program_transform_name=
+else
+ # Double any \ or $. echo might interpret backslashes.
+ cat <<\EOF_SED > conftestsed
+s,\\,\\\\,g; s,\$,$$,g
+EOF_SED
+ program_transform_name="`echo $program_transform_name|sed -f conftestsed`"
+ rm -f conftestsed
+fi
+test "$program_prefix" != NONE &&
+ program_transform_name="s,^,${program_prefix},; $program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+ program_transform_name="s,\$\$,${program_suffix},; $program_transform_name"
+
+# sed with no file args requires a program.
+test "$program_transform_name" = "" && program_transform_name="s,x,x,"
+
+
+debugging_support=no
+# Check whether --enable-debug or --disable-debug was given.
+if test "${enable_debug+set}" = set; then
+ enableval="$enable_debug"
+ if test "$enableval" = yes; then
+ debugging_support=yes
+ cat >> confdefs.h <<\EOF
+#define DEBUG 1
+EOF
+
+ fi
+
+fi
+
+
+
+if test "x$FRIBIDI_CONFIG" = x; then
+ # Extract the first word of "fribidi-config", so it can be a program name with args.
+set dummy fribidi-config; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:1439: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_FRIBIDI_CONFIG'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$FRIBIDI_CONFIG" in
+ /*)
+ ac_cv_path_FRIBIDI_CONFIG="$FRIBIDI_CONFIG" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_FRIBIDI_CONFIG="$FRIBIDI_CONFIG" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_FRIBIDI_CONFIG="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+FRIBIDI_CONFIG="$ac_cv_path_FRIBIDI_CONFIG"
+if test -n "$FRIBIDI_CONFIG"; then
+ echo "$ac_t""$FRIBIDI_CONFIG" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+test "x$FRIBIDI_CONFIG" = x && { echo "configure: error: Can't locate the fribidi-config program" 1>&2; exit 1; }
+test ! -x "$FRIBIDI_CONFIG" && { echo "configure: error: Can't execute the program '$FRIBIDI_CONFIG'" 1>&2; exit 1; }
+
+
+FRIBIDI_CXXFLAGS=`$FRIBIDI_CONFIG --cflags`
+if test "x$FRIBIDI_CXXFLAGS" = x-I/usr/include; then
+ ac_safe=`echo "fribidi/fribidi.h" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for fribidi/fribidi.h""... $ac_c" 1>&6
+echo "configure:1481: checking for fribidi/fribidi.h" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1486 "configure"
+#include "confdefs.h"
+#include <fribidi/fribidi.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1491: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+CXXFLAGS="$CXXFLAGS $FRIBIDI_CXXFLAGS"
+fi
+
+else
+ CXXFLAGS="$CXXFLAGS $FRIBIDI_CXXFLAGS"
+fi
+LDFLAGS="$LDFLAGS `$FRIBIDI_CONFIG --libs`"
+
+for ac_func in fribidi_log2vis fribidi_log2vis_get_embedding_levels
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:1521: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1526 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1552: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: A required FriBiDi function doesn't exist" 1>&2; exit 1; }
+fi
+done
+
+
+echo $ac_n "checking your FriBiDi library using a short test program""... $ac_c" 1>&6
+echo "configure:1579: checking your FriBiDi library using a short test program" >&5
+if test "$cross_compiling" = yes; then
+ { echo "configure: error: Can't cross-compile this test" 1>&2; exit 1; }
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1584 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+
+#include <fribidi/fribidi.h>
+
+typedef FriBidiChar unichar;
+typedef FriBidiCharType ctype_t;
+typedef FriBidiLevel level_t;
+typedef FriBidiStrIndex idx_t;
+
+int main()
+{
+ unichar unistr[] = { 0x5D0, 0x5D1, 0x5D2, 'A', 0 }; /* alef, bet, gimel, 'A' */
+#define UNISTRLEN 4
+ level_t levels[UNISTRLEN];
+ unichar deststr[UNISTRLEN + 1];
+ ctype_t ctype;
+ ctype_t base_dir = FRIBIDI_TYPE_RTL;
+ unichar lparen = '(';
+ unichar lparen_mirror;
+
+ /* make sure all these constants are defined. */
+ ctype = FRIBIDI_TYPE_LTR | FRIBIDI_TYPE_RTL | FRIBIDI_TYPE_ON | FRIBIDI_TYPE_NSM;
+
+ if (sizeof(unichar) != 4) {
+ printf("\nERROR: FriBiDi doesn't use UCS-4\n");
+ /* I'm planning support for 16 bit chars someday. */
+ return 1;
+ }
+
+ ctype = fribidi_get_type(unistr[0]);
+ if (!FRIBIDI_IS_LETTER(ctype) ||
+ !FRIBIDI_IS_RTL(ctype) ||
+ FRIBIDI_IS_SPACE(ctype) ||
+ FRIBIDI_IS_NUMBER(ctype))
+ {
+ printf("\nERROR: Wrong properties for the Hebrew letter Alef\n");
+ return 1;
+ }
+
+ fribidi_log2vis_get_embedding_levels(unistr, UNISTRLEN, &base_dir, levels);
+ if (levels[0] != 1 ||
+ levels[1] != 1 ||
+ levels[2] != 1 ||
+ levels[3] != 2)
+ {
+ printf("\nERROR: fribidi_log2vis_get_embedding_levels() "
+ "gives wrong results\n");
+ return 1;
+ }
+
+ fribidi_log2vis(unistr, UNISTRLEN, &base_dir, deststr, NULL, NULL, NULL);
+ if (deststr[0] != 'A' ||
+ deststr[1] != 0x5D2 ||
+ deststr[2] != 0x5D1 ||
+ deststr[3] != 0x5D0)
+ {
+ printf("\nERROR: fribidi_log2vis() gives wrong results\n");
+ return 1;
+ }
+
+ if (!fribidi_get_mirror_char(lparen, &lparen_mirror) ||
+ lparen_mirror != ')')
+ {
+ printf("\nERROR: fribidi_get_mirror_char() gives wrong results\n");
+ return 1;
+ }
+
+ return 0;
+}
+EOF
+if { (eval echo configure:1656: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ echo "$ac_t""OK" 1>&6
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ { echo "configure: error: The test program failed; check 'config.log'. You probably have an old version of FriBiDi; please upgrade." 1>&2; exit 1; }
+fi
+rm -fr conftest*
+fi
+
+
+
+# Check whether --with-curses or --without-curses was given.
+if test "${with_curses+set}" = set; then
+ withval="$with_curses"
+
+ case $withval in
+ no)
+ { echo "configure: error: I can only use curses, so don't specify --without-curses!" 1>&2; exit 1; };;
+ yes) ;;
+ *)
+ if test "x$withval" != x/usr; then
+ LDFLAGS="-L${withval}/lib $LDFLAGS"
+ CPPFLAGS="$CPPFLAGS -I${withval}/include"
+ fi;;
+ esac
+
+fi
+
+
+curses_lib=ncursesw
+curses_header=
+
+# First, check for ncursesw
+
+echo $ac_n "checking for waddnwstr in -lncursesw""... $ac_c" 1>&6
+echo "configure:1694: checking for waddnwstr in -lncursesw" >&5
+ac_lib_var=`echo ncursesw'_'waddnwstr | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lncursesw $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1702 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char waddnwstr();
+
+int main() {
+waddnwstr()
+; return 0; }
+EOF
+if { (eval echo configure:1716: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo ncursesw | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lncursesw $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+curses_lib=
+fi
+
+if test "x$curses_lib" = xncursesw; then
+ # look for ncursesw headers
+ for ac_hdr in ncursesw/ncurses.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:1750: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1755 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1760: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ curses_header="ncursesw/ncurses.h"
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ if test "x$curses_header" = x; then
+ for ac_hdr in ncurses.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:1791: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1796 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1801: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ curses_header="ncurses.h"
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: Can't find ncursesw's header file!" 1>&2; exit 1; }
+fi
+done
+
+ fi
+ # make sure it is indeed the header of the wchar_t version.
+ cat > conftest.$ac_ext <<EOF
+#line 1831 "configure"
+#include "confdefs.h"
+#define _XOPEN_SOURCE_EXTENDED 1
+ #include <$curses_header>
+int main() {
+
+ wchar_t ch;
+ waddnwstr((WINDOW*)0, &ch, 1);
+
+; return 0; }
+EOF
+if { (eval echo configure:1842: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ :
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ { echo "configure: error: I can't find ncursesw's header file! please check config.log" 1>&2; exit 1; }
+fi
+rm -f conftest*
+ cat >> confdefs.h <<\EOF
+#define HAVE_WIDE_CURSES 1
+EOF
+
+else
+ curses_lib=ncurses
+ for ac_hdr in curses.h ncurses.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:1861: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1866 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1871: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ echo $ac_n "checking for initscr in -lncurses""... $ac_c" 1>&6
+echo "configure:1898: checking for initscr in -lncurses" >&5
+ac_lib_var=`echo ncurses'_'initscr | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lncurses $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1906 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char initscr();
+
+int main() {
+initscr()
+; return 0; }
+EOF
+if { (eval echo configure:1920: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo ncurses | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lncurses $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+curses_lib=
+fi
+
+ if test "x$curses_lib" = x; then
+ curses_lib=curses
+ echo $ac_n "checking for initscr in -lcurses""... $ac_c" 1>&6
+echo "configure:1951: checking for initscr in -lcurses" >&5
+ac_lib_var=`echo curses'_'initscr | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lcurses $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1959 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char initscr();
+
+int main() {
+initscr()
+; return 0; }
+EOF
+if { (eval echo configure:1973: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo curses | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lcurses $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+curses_lib=
+fi
+
+ fi
+ if test "x$curses_lib" = x; then
+ { echo "configure: error: No curses/ncurses was found" 1>&2; exit 1; }
+ fi
+fi
+
+# test for the existance of particular curses functions
+
+for ac_func in use_default_colors start_color curs_set
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2012: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2017 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2043: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+if test "$ac_cv_func_start_color" = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_COLOR 1
+EOF
+
+fi
+
+
+# rudimentary gettext check, till I utilize automake's gettext support
+
+for ac_hdr in libintl.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:2081: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2086 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:2091: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ for ac_func in gettext
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2114: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2119 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2145: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+ cat >> confdefs.h <<\EOF
+#define USE_GETTEXT 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+# locale support
+
+for ac_hdr in locale.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:2184: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2189 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:2194: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+for ac_func in setlocale
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2224: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2229 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2255: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+echo $ac_n "checking for nl_langinfo and CODESET""... $ac_c" 1>&6
+echo "configure:2281: checking for nl_langinfo and CODESET" >&5
+cat > conftest.$ac_ext <<EOF
+#line 2283 "configure"
+#include "confdefs.h"
+#include <langinfo.h>
+int main() {
+char* s = nl_langinfo(CODESET);
+; return 0; }
+EOF
+if { (eval echo configure:2290: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+
+ echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define HAVE_LANGINFO_CODESET 1
+EOF
+
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ echo "$ac_t""no" 1>&6
+fi
+rm -f conftest*
+
+
+# Check whether --with-iconv or --without-iconv was given.
+if test "${with_iconv+set}" = set; then
+ withval="$with_iconv"
+
+ case $withval in
+ no) skip_iconv=yes ;;
+ yes) ;;
+ *)
+ if test "x$withval" != x/usr; then
+ LDFLAGS="-L${withval}/lib $LDFLAGS"
+ CPPFLAGS="$CPPFLAGS -I${withval}/include"
+ fi;;
+ esac
+
+fi
+
+
+use_iconv=no
+default_encoding=ISO-8859-8
+
+if test "x$skip_iconv" != xyes; then
+for ac_hdr in iconv.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:2333: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2338 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:2343: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+ echo $ac_n "checking whether byte ordering is bigendian""... $ac_c" 1>&6
+echo "configure:2365: checking whether byte ordering is bigendian" >&5
+if eval "test \"`echo '$''{'ac_cv_c_bigendian'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_cv_c_bigendian=unknown
+# See if sys/param.h defines the BYTE_ORDER macro.
+cat > conftest.$ac_ext <<EOF
+#line 2372 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+
+#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN
+ bogus endian macros
+#endif
+; return 0; }
+EOF
+if { (eval echo configure:2383: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ # It does; now see whether it defined to BIG_ENDIAN or not.
+cat > conftest.$ac_ext <<EOF
+#line 2387 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+#endif
+; return 0; }
+EOF
+if { (eval echo configure:2398: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_c_bigendian=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_c_bigendian=no
+fi
+rm -f conftest*
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+if test $ac_cv_c_bigendian = unknown; then
+if test "$cross_compiling" = yes; then
+ { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; }
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2418 "configure"
+#include "confdefs.h"
+main () {
+ /* Are we little or big endian? From Harbison&Steele. */
+ union
+ {
+ long l;
+ char c[sizeof (long)];
+ } u;
+ u.l = 1;
+ exit (u.c[sizeof (long) - 1] == 1);
+}
+EOF
+if { (eval echo configure:2431: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_c_bigendian=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_c_bigendian=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+fi
+
+echo "$ac_t""$ac_cv_c_bigendian" 1>&6
+if test $ac_cv_c_bigendian = yes; then
+ cat >> confdefs.h <<\EOF
+#define WORDS_BIGENDIAN 1
+EOF
+
+fi
+
+ case $ac_cv_c_bigendian in
+ yes) internal_encoding=UCS-4BE ;;
+ no) internal_encoding=UCS-4LE ;;
+ *) { echo "configure: error: Can't determine machine endianess" 1>&2; exit 1; }
+ esac
+
+ echo $ac_n "checking for iconv_open in -liconv""... $ac_c" 1>&6
+echo "configure:2461: checking for iconv_open in -liconv" >&5
+ac_lib_var=`echo iconv'_'iconv_open | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-liconv $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2469 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char iconv_open();
+
+int main() {
+iconv_open()
+; return 0; }
+EOF
+if { (eval echo configure:2483: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo iconv | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-liconv $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ for encoding in UTF-8 ISO-8859-8 CP1255; do
+ tr_encoding=`echo $encoding | tr - _`
+ echo $ac_n "checking whether iconv supports the $encoding encoding""... $ac_c" 1>&6
+echo "configure:2513: checking whether iconv supports the $encoding encoding" >&5
+ if test "$cross_compiling" = yes; then
+ { echo "configure: error: Can't cross-compile this test" 1>&2; exit 1; }
+
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2519 "configure"
+#include "confdefs.h"
+#include <iconv.h>
+ int main()
+ {
+ iconv_t cd = iconv_open("$internal_encoding", "$encoding");
+ return (cd == (iconv_t)-1);
+ }
+EOF
+if { (eval echo configure:2528: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+
+ echo "$ac_t""yes" 1>&6
+ eval "have_encoding_${tr_encoding}=yes"
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+
+ echo "$ac_t""no" 1>&6
+ eval "have_encoding_${tr_encoding}=no"
+
+fi
+rm -fr conftest*
+fi
+
+ done
+ if test "$have_encoding_UTF_8" = yes -a "$have_encoding_ISO_8859_8" = yes; then
+ use_iconv=yes
+ if test "$have_encoding_CP1255" = yes; then
+ default_encoding=CP1255
+ else
+ default_encoding=ISO-8859-8
+ fi
+
+ echo $ac_n "checking whether your iconv() needs const""... $ac_c" 1>&6
+echo "configure:2556: checking whether your iconv() needs const" >&5
+ iconv_const=
+ cat > conftest.$ac_ext <<EOF
+#line 2559 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+ #include <iconv.h>
+int main() {
+iconv((iconv_t)-1,(const char **)NULL,NULL,NULL,NULL);
+; return 0; }
+EOF
+if { (eval echo configure:2567: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ cat > conftest.$ac_ext <<EOF
+#line 2570 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+ #include <iconv.h>
+int main() {
+iconv((iconv_t)-1,(char **)NULL,NULL,NULL,NULL);
+; return 0; }
+EOF
+if { (eval echo configure:2578: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ :
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ iconv_const=const
+
+fi
+rm -f conftest*
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+ echo "$ac_t""$iconv_const" 1>&6
+
+ cat >> confdefs.h <<\EOF
+#define USE_ICONV 1
+EOF
+
+ cat >> confdefs.h <<EOF
+#define ICONV_CONST $iconv_const
+EOF
+
+ cat >> confdefs.h <<EOF
+#define INTERNAL_ENCODING "$internal_encoding"
+EOF
+
+ fi
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+cat >> confdefs.h <<EOF
+#define DEFAULT_FILE_ENCODING "$default_encoding"
+EOF
+
+
+for ac_func in wctob btowc
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2625: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2630 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2656: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+
+for ac_func in getopt_long
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2685: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2690 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2716: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ac_header_dirent=no
+for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6
+echo "configure:2745: checking for $ac_hdr that defines DIR" >&5
+if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2750 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <$ac_hdr>
+int main() {
+DIR *dirp = 0;
+; return 0; }
+EOF
+if { (eval echo configure:2758: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ ac_header_dirent=$ac_hdr; break
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix.
+if test $ac_header_dirent = dirent.h; then
+echo $ac_n "checking for opendir in -ldir""... $ac_c" 1>&6
+echo "configure:2783: checking for opendir in -ldir" >&5
+ac_lib_var=`echo dir'_'opendir | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldir $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2791 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char opendir();
+
+int main() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:2805: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -ldir"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+echo $ac_n "checking for opendir in -lx""... $ac_c" 1>&6
+echo "configure:2827: checking for opendir in -lx" >&5
+ac_lib_var=`echo x'_'opendir | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lx $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2835 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char opendir();
+
+int main() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:2849: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -lx"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+for ac_func in strerror strstr strtol vprintf
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2874: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2879 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2905: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: A required function does not exist" 1>&2; exit 1; }
+fi
+done
+
+for ac_func in vsnprintf vasprintf
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2933: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2938 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2964: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
+echo "configure:2989: checking for ANSI C header files" >&5
+if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2994 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:3002: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ ac_cv_header_stdc=yes
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 3019 "configure"
+#include "confdefs.h"
+#include <string.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "memchr" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 3037 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "free" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+if test "$cross_compiling" = yes; then
+ :
+else
+ cat > conftest.$ac_ext <<EOF
+#line 3058 "configure"
+#include "confdefs.h"
+#include <ctype.h>
+#define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int main () { int i; for (i = 0; i < 256; i++)
+if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2);
+exit (0); }
+
+EOF
+if { (eval echo configure:3069: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ :
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_header_stdc=no
+fi
+rm -fr conftest*
+fi
+
+fi
+fi
+
+echo "$ac_t""$ac_cv_header_stdc" 1>&6
+if test $ac_cv_header_stdc = yes; then
+ cat >> confdefs.h <<\EOF
+#define STDC_HEADERS 1
+EOF
+
+fi
+
+echo $ac_n "checking for mode_t""... $ac_c" 1>&6
+echo "configure:3093: checking for mode_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 3098 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])mode_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_mode_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_mode_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_mode_t" 1>&6
+if test $ac_cv_type_mode_t = no; then
+ cat >> confdefs.h <<\EOF
+#define mode_t int
+EOF
+
+fi
+
+
+cat >> confdefs.h <<\EOF
+#define RETSIGTYPE void
+EOF
+
+
+
+cat <<MSG
+
+ Results:
+ --------
+ curses library: $curses_lib
+ use iconv: $use_iconv
+ default file encoding: $default_encoding
+ (debugging support: $debugging_support)
+
+MSG
+if ! test "$curses_lib" = ncursesw; then
+ cat <<MSG
+ WARNING: you're using $curses_lib, not ncursesw, so you won't be able to use
+ $PACKAGE in the UTF-8 locale.
+
+MSG
+fi
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs. It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already. You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote substitution
+ # turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ -e "s/'/'\\\\''/g" \
+ -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p'
+ ;;
+ esac >> confcache
+if cmp -s $cache_file confcache; then
+ :
+else
+ if test -w $cache_file; then
+ echo "updating cache $cache_file"
+ cat confcache > $cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+DEFS=-DHAVE_CONFIG_H
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+ case "\$ac_option" in
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+ exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+ -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+ echo "$CONFIG_STATUS generated by autoconf version 2.13"
+ exit 0 ;;
+ -help | --help | --hel | --he | --h)
+ echo "\$ac_cs_usage"; exit 0 ;;
+ *) echo "\$ac_cs_usage"; exit 1 ;;
+ esac
+done
+
+ac_given_srcdir=$srcdir
+ac_given_INSTALL="$INSTALL"
+
+trap 'rm -fr `echo "Makefile pgeresh config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@SHELL@%$SHELL%g
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@FFLAGS@%$FFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g
+s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g
+s%@INSTALL_DATA@%$INSTALL_DATA%g
+s%@PACKAGE@%$PACKAGE%g
+s%@VERSION@%$VERSION%g
+s%@ACLOCAL@%$ACLOCAL%g
+s%@AUTOCONF@%$AUTOCONF%g
+s%@AUTOMAKE@%$AUTOMAKE%g
+s%@AUTOHEADER@%$AUTOHEADER%g
+s%@MAKEINFO@%$MAKEINFO%g
+s%@SET_MAKE@%$SET_MAKE%g
+s%@CC@%$CC%g
+s%@CPP@%$CPP%g
+s%@CXX@%$CXX%g
+s%@CXXCPP@%$CXXCPP%g
+s%@FRIBIDI_CONFIG@%$FRIBIDI_CONFIG%g
+
+CEOF
+EOF
+
+cat >> $CONFIG_STATUS <<\EOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+ else
+ sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+ fi
+ if test ! -s conftest.s$ac_file; then
+ ac_more_lines=false
+ rm -f conftest.s$ac_file
+ else
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f conftest.s$ac_file"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+ fi
+ ac_file=`expr $ac_file + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_cmds`
+ fi
+done
+if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+fi
+EOF
+
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile pgeresh"}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+ # Remove last slash and all that follows it. Not all systems have dirname.
+ ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+ if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+ # The file is in a subdirectory.
+ test ! -d "$ac_dir" && mkdir "$ac_dir"
+ ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+ else
+ ac_dir_suffix= ac_dots=
+ fi
+
+ case "$ac_given_srcdir" in
+ .) srcdir=.
+ if test -z "$ac_dots"; then top_srcdir=.
+ else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+ /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+ *) # Relative path.
+ srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+ top_srcdir="$ac_dots$ac_given_srcdir" ;;
+ esac
+
+ case "$ac_given_INSTALL" in
+ [/$]*) INSTALL="$ac_given_INSTALL" ;;
+ *) INSTALL="$ac_dots$ac_given_INSTALL" ;;
+ esac
+
+ echo creating "$ac_file"
+ rm -f "$ac_file"
+ configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+ case "$ac_file" in
+ *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+ *) ac_comsub= ;;
+ esac
+
+ ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+ sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+s%@INSTALL@%$INSTALL%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)'
+ac_dB='\([ ][ ]*\)[^ ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_uB='\([ ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+if test "${CONFIG_HEADERS+set}" != set; then
+EOF
+cat >> $CONFIG_STATUS <<EOF
+ CONFIG_HEADERS="config.h"
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+fi
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ echo creating $ac_file
+
+ rm -f conftest.frag conftest.in conftest.out
+ ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+ cat $ac_file_inputs > conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h. And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments. This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+
+rm -f conftest.tail
+while :
+do
+ ac_lines=`grep -c . conftest.vals`
+ # grep -c gives empty output for an empty file on some AIX systems.
+ if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+ # Write a limited-size here document to conftest.frag.
+ echo ' cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+ sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+ echo 'CEOF
+ sed -f conftest.frag conftest.in > conftest.out
+ rm -f conftest.in
+ mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+ sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+ rm -f conftest.vals
+ mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+ rm -f conftest.frag conftest.h
+ echo "/* $ac_file. Generated automatically by configure. */" > conftest.h
+ cat conftest.in >> conftest.h
+ rm -f conftest.in
+ if cmp -s $ac_file conftest.h 2>/dev/null; then
+ echo "$ac_file is unchanged"
+ rm -f conftest.h
+ else
+ # Remove last slash and all that follows it. Not all systems have dirname.
+ ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+ if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+ # The file is in a subdirectory.
+ test ! -d "$ac_dir" && mkdir "$ac_dir"
+ fi
+ rm -f $ac_file
+ mv conftest.h $ac_file
+ fi
+fi; done
+
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+test -z "$CONFIG_HEADERS" || echo timestamp > stamp-h
+chmod +x pgeresh
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..03cc091
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,316 @@
+AC_INIT(main.cc)
+AM_INIT_AUTOMAKE(geresh, 0.6.3)
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_CXX
+AC_PROG_CXXCPP
+AC_PROG_INSTALL
+
+AC_LANG_CPLUSPLUS
+
+AC_ARG_PROGRAM
+
+debugging_support=no
+AC_ARG_ENABLE(debug,
+[ --enable-debug Enable debugging support],
+[ if test "$enableval" = yes; then
+ debugging_support=yes
+ AC_DEFINE(DEBUG)
+ fi
+])
+
+dnl ---- fribidi ----
+
+if test "x$FRIBIDI_CONFIG" = x; then
+ AC_PATH_PROG(FRIBIDI_CONFIG, fribidi-config)
+fi
+
+test "x$FRIBIDI_CONFIG" = x && AC_MSG_ERROR(Can't locate the fribidi-config program)
+test ! -x "$FRIBIDI_CONFIG" && AC_MSG_ERROR(Can't execute the program '$FRIBIDI_CONFIG')
+
+
+FRIBIDI_CXXFLAGS=`$FRIBIDI_CONFIG --cflags`
+if test "x$FRIBIDI_CXXFLAGS" = x-I/usr/include; then
+ AC_CHECK_HEADER(fribidi/fribidi.h, , [CXXFLAGS="$CXXFLAGS $FRIBIDI_CXXFLAGS"])
+else
+ CXXFLAGS="$CXXFLAGS $FRIBIDI_CXXFLAGS"
+fi
+LDFLAGS="$LDFLAGS `$FRIBIDI_CONFIG --libs`"
+
+AC_CHECK_FUNCS(fribidi_log2vis fribidi_log2vis_get_embedding_levels, ,
+ [AC_MSG_ERROR(A required FriBiDi function doesn't exist)])
+
+AC_MSG_CHECKING(your FriBiDi library using a short test program)
+AC_TRY_RUN(
+[#include <stdio.h>
+
+#include <fribidi/fribidi.h>
+
+typedef FriBidiChar unichar;
+typedef FriBidiCharType ctype_t;
+typedef FriBidiLevel level_t;
+typedef FriBidiStrIndex idx_t;
+
+int main()
+{
+ unichar unistr[] = { 0x5D0, 0x5D1, 0x5D2, 'A', 0 }; /* alef, bet, gimel, 'A' */
+#define UNISTRLEN 4
+ level_t levels[UNISTRLEN];
+ unichar deststr[UNISTRLEN + 1];
+ ctype_t ctype;
+ ctype_t base_dir = FRIBIDI_TYPE_RTL;
+ unichar lparen = '(';
+ unichar lparen_mirror;
+
+ /* make sure all these constants are defined. */
+ ctype = FRIBIDI_TYPE_LTR | FRIBIDI_TYPE_RTL | FRIBIDI_TYPE_ON | FRIBIDI_TYPE_NSM;
+
+ if (sizeof(unichar) != 4) {
+ printf("\nERROR: FriBiDi doesn't use UCS-4\n");
+ /* I'm planning support for 16 bit chars someday. */
+ return 1;
+ }
+
+ ctype = fribidi_get_type(unistr[0]);
+ if (!FRIBIDI_IS_LETTER(ctype) ||
+ !FRIBIDI_IS_RTL(ctype) ||
+ FRIBIDI_IS_SPACE(ctype) ||
+ FRIBIDI_IS_NUMBER(ctype))
+ {
+ printf("\nERROR: Wrong properties for the Hebrew letter Alef\n");
+ return 1;
+ }
+
+ fribidi_log2vis_get_embedding_levels(unistr, UNISTRLEN, &base_dir, levels);
+ if (levels[0] != 1 ||
+ levels[1] != 1 ||
+ levels[2] != 1 ||
+ levels[3] != 2)
+ {
+ printf("\nERROR: fribidi_log2vis_get_embedding_levels() "
+ "gives wrong results\n");
+ return 1;
+ }
+
+ fribidi_log2vis(unistr, UNISTRLEN, &base_dir, deststr, NULL, NULL, NULL);
+ if (deststr[0] != 'A' ||
+ deststr[1] != 0x5D2 ||
+ deststr[2] != 0x5D1 ||
+ deststr[3] != 0x5D0)
+ {
+ printf("\nERROR: fribidi_log2vis() gives wrong results\n");
+ return 1;
+ }
+
+ if (!fribidi_get_mirror_char(lparen, &lparen_mirror) ||
+ lparen_mirror != ')')
+ {
+ printf("\nERROR: fribidi_get_mirror_char() gives wrong results\n");
+ return 1;
+ }
+
+ return 0;
+}],
+[AC_MSG_RESULT(OK)],
+[AC_MSG_ERROR(The test program failed; check 'config.log'. You probably have an old version of FriBiDi; please upgrade.)],
+[AC_MSG_ERROR(Can't cross-compile this test)])
+
+dnl ---- curses ----
+
+AC_ARG_WITH(curses,
+[ --with-curses=DIR Where [n]curses[w] is installed ],
+[
+ case $withval in
+ no)
+ AC_MSG_ERROR([I can only use curses, so don't specify --without-curses!]);;
+ yes) ;;
+ *)
+ if test "x$withval" != x/usr; then
+ LDFLAGS="-L${withval}/lib $LDFLAGS"
+ CPPFLAGS="$CPPFLAGS -I${withval}/include"
+ fi;;
+ esac
+])
+
+curses_lib=ncursesw
+curses_header=
+
+# First, check for ncursesw
+
+AC_CHECK_LIB(ncursesw, waddnwstr, , [curses_lib=])
+if test "x$curses_lib" = xncursesw; then
+ # look for ncursesw headers
+ AC_CHECK_HEADERS(ncursesw/ncurses.h, [curses_header="ncursesw/ncurses.h"])
+ if test "x$curses_header" = x; then
+ AC_CHECK_HEADERS(ncurses.h, [curses_header="ncurses.h"],
+ [AC_MSG_ERROR(Can't find ncursesw's header file!)])
+ fi
+ # make sure it is indeed the header of the wchar_t version.
+ AC_TRY_LINK([#define _XOPEN_SOURCE_EXTENDED 1
+ #include <$curses_header>],
+ [
+ wchar_t ch;
+ waddnwstr((WINDOW*)0, &ch, 1);
+ ], ,
+ [AC_MSG_ERROR(I can't find ncursesw's header file! please check config.log)])
+ AC_DEFINE(HAVE_WIDE_CURSES)
+else
+ curses_lib=ncurses
+ AC_CHECK_HEADERS(curses.h ncurses.h)
+ AC_CHECK_LIB(ncurses, initscr, , [curses_lib=])
+ if test "x$curses_lib" = x; then
+ curses_lib=curses
+ AC_CHECK_LIB(curses, initscr, , [curses_lib=])
+ fi
+ if test "x$curses_lib" = x; then
+ AC_MSG_ERROR(No curses/ncurses was found)
+ fi
+fi
+
+# test for the existance of particular curses functions
+
+AC_CHECK_FUNCS(use_default_colors start_color curs_set)
+if test "$ac_cv_func_start_color" = yes; then
+ AC_DEFINE(HAVE_COLOR, 1)
+fi
+
+dnl ---- gettext ----
+
+# rudimentary gettext check, till I utilize automake's gettext support
+
+AC_CHECK_HEADERS(libintl.h, [AC_CHECK_FUNCS(gettext, [AC_DEFINE(USE_GETTEXT)])])
+
+# locale support
+
+AC_CHECK_HEADERS(locale.h)
+
+AC_CHECK_FUNCS(setlocale)
+
+AC_MSG_CHECKING(for nl_langinfo and CODESET)
+AC_TRY_LINK([#include <langinfo.h>],
+ [char* s = nl_langinfo(CODESET);],
+ [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_LANGINFO_CODESET)
+ ],
+ [AC_MSG_RESULT(no)])
+
+dnl ---- iconv ----
+
+AC_ARG_WITH(iconv,
+[ --with-iconv=DIR Where iconv is installed ],
+[
+ case $withval in
+ no) skip_iconv=yes ;;
+ yes) ;;
+ *)
+ if test "x$withval" != x/usr; then
+ LDFLAGS="-L${withval}/lib $LDFLAGS"
+ CPPFLAGS="$CPPFLAGS -I${withval}/include"
+ fi;;
+ esac
+])
+
+use_iconv=no
+default_encoding=ISO-8859-8
+
+if test "x$skip_iconv" != xyes; then
+AC_CHECK_HEADERS(iconv.h,
+[
+ AC_C_BIGENDIAN
+ case $ac_cv_c_bigendian in
+ yes) internal_encoding=UCS-4BE ;;
+ no) internal_encoding=UCS-4LE ;;
+ *) AC_MSG_ERROR(Can't determine machine endianess)
+ esac
+
+ AC_CHECK_LIB(iconv, iconv_open)
+ for encoding in UTF-8 ISO-8859-8 CP1255; do
+ tr_encoding=`echo $encoding | tr - _`
+ AC_MSG_CHECKING(whether iconv supports the $encoding encoding)
+ AC_TRY_RUN([#include <iconv.h>
+ int main()
+ {
+ iconv_t cd = iconv_open("$internal_encoding", "$encoding");
+ return (cd == (iconv_t)-1);
+ }],
+ [
+ AC_MSG_RESULT(yes)
+ eval "have_encoding_${tr_encoding}=yes"
+ ],
+ [
+ AC_MSG_RESULT(no)
+ eval "have_encoding_${tr_encoding}=no"
+ ],
+ [AC_MSG_ERROR(Can't cross-compile this test)]
+ )
+ done
+ if test "$have_encoding_UTF_8" = yes -a "$have_encoding_ISO_8859_8" = yes; then
+ use_iconv=yes
+ if test "$have_encoding_CP1255" = yes; then
+ default_encoding=CP1255
+ else
+ default_encoding=ISO-8859-8
+ fi
+
+ AC_MSG_CHECKING(whether your iconv() needs const)
+ iconv_const=
+ AC_TRY_LINK([#include <stdlib.h>
+ #include <iconv.h>],
+ [iconv((iconv_t)-1,(const char **)NULL,NULL,NULL,NULL);],
+ AC_TRY_LINK([#include <stdlib.h>
+ #include <iconv.h>],
+ [iconv((iconv_t)-1,(char **)NULL,NULL,NULL,NULL);],
+ ,
+ iconv_const=const
+ )
+ )
+ AC_MSG_RESULT($iconv_const)
+
+ AC_DEFINE(USE_ICONV)
+ AC_DEFINE_UNQUOTED(ICONV_CONST, $iconv_const)
+ AC_DEFINE_UNQUOTED(INTERNAL_ENCODING, "$internal_encoding")
+ fi
+])
+fi
+
+AC_DEFINE_UNQUOTED(DEFAULT_FILE_ENCODING, "$default_encoding")
+
+AC_CHECK_FUNCS(wctob btowc)
+
+dnl ---- misc ----
+
+AC_CHECK_FUNCS(getopt_long)
+AC_HEADER_DIRENT
+dnl AC_CHEK_HEADERS(fcntl.h unistd.h)
+AC_CHECK_FUNCS(strerror strstr strtol vprintf, ,
+ AC_MSG_ERROR([A required function does not exist]))
+AC_CHECK_FUNCS(vsnprintf vasprintf)
+AC_TYPE_MODE_T
+
+dnl AC_TYPE_SIGNAL - fails on some systems, so:
+AC_DEFINE(RETSIGTYPE, void)
+
+dnl ---- print out the results ----
+
+cat <<MSG
+
+ Results:
+ --------
+ curses library: $curses_lib
+ use iconv: $use_iconv
+ default file encoding: $default_encoding
+ (debugging support: $debugging_support)
+
+MSG
+if ! test "$curses_lib" = ncursesw; then
+ cat <<MSG
+ WARNING: you're using $curses_lib, not ncursesw, so you won't be able to use
+ $PACKAGE in the UTF-8 locale.
+
+MSG
+fi
+
+AC_OUTPUT(Makefile pgeresh, [chmod +x pgeresh])
diff --git a/converters.cc b/converters.cc
new file mode 100644
index 0000000..f126072
--- /dev/null
+++ b/converters.cc
@@ -0,0 +1,295 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <ctype.h> // toupper
+
+#include "converters.h"
+#include "iso88598.h"
+#include "utf8.h"
+#include "dbg.h"
+
+// guess_encoding() - guesses the encoding of a string.
+
+const char *guess_encoding(const char *buf, int len)
+{
+#define IS_UTF8 "UTF-8"
+#define IS_UTF16 "UTF-16"
+#define IS_UTF32 "UTF-32"
+#define IS_NOT_UTF8 NULL
+#define UNKNOWN_TYPE NULL
+
+ if (len >= 4) {
+ // check for BOM (Byte-Order Mark):
+ //
+ // UTF-16 big-endian FE FF
+ // UTF-16 little-endian FF FE
+ // UTF-32 big-endian 00 00 FE FF
+ // UTF-32 little-endian FF FE 00 00
+
+ if (buf[0] == (char)0xFE && buf[1] == (char)0xFF)
+ return IS_UTF16; // BE
+ if (buf[0] == (char)0xFF && buf[1] == (char)0xFE) {
+ if (buf[2] == 0 && buf[3] == 0)
+ return IS_UTF32; // LE
+ else
+ return IS_UTF16; // LE
+ }
+ if (buf[0] == 0 && buf[1] == 0
+ && buf[2] == (char)0xFE && buf[3] == (char)0xFF)
+ return IS_UTF32; // BE
+ }
+
+ // No BOM was found. Go through the string and check for
+ // UTF-8 sequences: if an illegal sequence was found, it's
+ // IS_NOT_UTF8, if all squences are legal, it's IS_UTF8, if
+ // it doesn't have characters with high bit set, return
+ // UNKNOWN.
+
+ const char *result = UNKNOWN_TYPE;
+ int nbytes = 0;
+ for (int i = 0; i < len; i++) {
+ if (nbytes) {
+ if ((buf[i] & 0xC0) != 0x80) {
+ return IS_NOT_UTF8;
+ }
+ if (! --nbytes)
+ result = IS_UTF8;
+ } else if (buf[i] & 0x80) {
+ const char &c = buf[i];
+ nbytes = (c & 0xE0) == 0xC0 ? 1 :
+ (c & 0xF0) == 0xE0 ? 2 :
+ (c & 0xF8) == 0xF0 ? 3 :
+ (c & 0xFC) == 0xF8 ? 4 :
+ (c & 0xFE) == 0xFC ? 5 : 0;
+ if (nbytes == 0) {
+ return IS_NOT_UTF8;
+ }
+ }
+ }
+ return result;
+}
+
+#ifdef USE_ICONV
+IconvConverter::IconvConverter()
+{
+ cd = (iconv_t)-1;
+}
+
+IconvConverter::~IconvConverter()
+{
+ if (cd != (iconv_t)-1)
+ iconv_close(cd);
+}
+
+int IconvConverter::set_source_encoding(const char *encoding)
+{
+ cd = iconv_open(INTERNAL_ENCODING, encoding);
+ if (cd == (iconv_t)-1) {
+ return false;
+ }
+ return true;
+}
+
+int IconvConverter::set_target_encoding(const char *encoding)
+{
+ cd = iconv_open(encoding, INTERNAL_ENCODING);
+ if (cd == (iconv_t)-1) {
+ return false;
+ }
+ return true;
+}
+
+int IconvConverter::convert(unichar **dest, char **src, int len)
+{
+ size_t dest_avail = 99999; // :FIXME: I've tried several xxx_MAX constants
+ // but it makes iconv() fail.
+ size_t src_bytes_left = len;
+ size_t result = iconv(cd, (ICONV_CONST char **)src, &src_bytes_left,
+ (char **)dest, &dest_avail);
+ return (result == (size_t)-1) ? (int)-1 : (int)result;
+}
+
+int IconvConverter::convert(char **dest, unichar **src, int len)
+{
+ size_t dest_avail = 99999; // :FIXME:
+ size_t src_bytes_left = len * sizeof(unichar);
+ size_t result;
+
+ while (1) {
+ result = iconv(cd, (ICONV_CONST char **)src, &src_bytes_left,
+ dest, &dest_avail);
+ if (ilseq_repr && result == (size_t)-1 && errno == EILSEQ) {
+ // We're asked to represent EILSEQ as "?".
+ // we put "?" in **dest, and advance the
+ // src pointer.
+ (*src)++;
+ src_bytes_left -= sizeof(unichar);
+ (**dest) = '?';
+ (*dest)++;
+ dest_avail--;
+ } else {
+ break;
+ }
+ }
+ return (result == (size_t)-1) ? (int)-1 : (int)result;
+}
+#endif
+
+int ISO88598Converter::convert(unichar **dest, char **src, int len)
+{
+ int count = 0;
+ unichar * &d = *dest;
+ char * &s = *src;
+ while (len--) {
+ *d++ = iso88598_to_unicode(*s++);
+ count++;
+ }
+ return count;
+}
+
+int ISO88598Converter::convert(char **dest, unichar **src, int len)
+{
+ int count = 0;
+ char * &d = *dest;
+ unichar * &s = *src;
+ while (len--) {
+ int ich = unicode_to_iso88598(*s);
+ if (ich == EOF) {
+ if (ilseq_repr) {
+ ich = '?';
+ } else {
+ errno = EILSEQ;
+ return -1;
+ }
+ }
+ *d++ = (char)ich;
+ s++;
+ count++;
+ }
+ return count;
+}
+
+int Latin1Converter::convert(unichar **dest, char **src, int len)
+{
+ int count = len;
+ unichar * &d = *dest;
+ char * &s = *src;
+ while (len--)
+ *d++ = (unsigned char)*s++;
+ return count;
+}
+
+int Latin1Converter::convert(char **dest, unichar **src, int len)
+{
+ int count = len;
+ char * &d = *dest;
+ unichar * &s = *src;
+ while (len--) {
+ if (*s > 0xFF) {
+ if (ilseq_repr) {
+ *d++ = '?';
+ s++;
+ } else {
+ errno = EILSEQ;
+ return -1;
+ }
+ } else {
+ *d++ = (char)*s++;
+ }
+ }
+ return count;
+}
+
+int UTF8Converter::convert(unichar **dest, char **src, int len)
+{
+ int count = 0;
+ unichar * &d = *dest;
+ char * &s = *src;
+ const char *problem;
+ count = utf8_to_unicode(d, s, len, &problem);
+ if (problem) {
+ d += count;
+ s = (char *)problem;
+ errno = EINVAL;
+ return -1;
+ } else {
+ d += count;
+ s += len;
+ }
+ return count;
+}
+
+int UTF8Converter::convert(char **dest, unichar **src, int len)
+{
+ char * &d = *dest;
+ unichar * &s = *src;
+ int nbytes = unicode_to_utf8(d, s, len);
+ d += nbytes;
+ s += len;
+ return len;
+}
+
+Converter *ConverterFactory::get_internal_converter(const char *enc)
+{
+ // canonize the encoding name: remove '-', and upperace.
+ u8string encoding = u8string(enc).erase_char('-').toupper_ascii();
+
+ DBG(1, ("looking for internal '%s' converter\n", encoding.c_str()));
+
+ if (encoding == "UTF8")
+ return new UTF8Converter();
+ if (encoding == "ISO88598" || encoding == "88598")
+ return new ISO88598Converter();
+ if (encoding == "ISO88591" || encoding == "LATIN1"
+ || encoding == "88591" || encoding == "ASCII"
+ || encoding == "USASCII")
+ return new Latin1Converter();
+
+ return NULL;
+}
+
+Converter *ConverterFactory::get_converter_from(const char *encoding)
+{
+#ifdef USE_ICONV
+ IconvConverter *iconv = new IconvConverter();
+ if (!iconv->set_source_encoding(encoding)) {
+ delete iconv;
+ return NULL;
+ } else {
+ return iconv;
+ }
+#else
+ return ConverterFactory::get_internal_converter(encoding);
+#endif
+}
+
+Converter *ConverterFactory::get_converter_to(const char *encoding)
+{
+#ifdef USE_ICONV
+ IconvConverter *iconv = new IconvConverter();
+ if (!iconv->set_target_encoding(encoding)) {
+ delete iconv;
+ return NULL;
+ } else {
+ return iconv;
+ }
+#else
+ return ConverterFactory::get_internal_converter(encoding);
+#endif
+}
+
diff --git a/converters.h b/converters.h
new file mode 100644
index 0000000..d2df61e
--- /dev/null
+++ b/converters.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_CONVERTERS_H
+#define BDE_CONVERTERS_H
+
+#include <config.h>
+
+#include <errno.h>
+#ifndef EILSEQ
+# define EILSEQ 2000
+#endif
+
+#ifdef USE_ICONV
+# include <iconv.h>
+#endif
+
+#include "types.h"
+
+// A Converter object converts between encodings.
+//
+// Converter (an abstract class)
+// |
+// +-- IconvConverter (used when the system has ICONV)
+// |
+// +-- ISO88598Converter (used when no ICONV presents)
+// +-- Latin1Converter ditto
+// +-- UTF8Converter ditto
+
+const char *guess_encoding(const char *buf, int len);
+
+class Converter {
+protected:
+ bool ilseq_repr;
+public:
+ // The Converter interface is based on iconv()'s interface.
+ // if a conversion function fails, it returns -1 and updates errno.
+ virtual int convert(unichar **dest, char **src, int len) = 0;
+ virtual int convert(char **dest, unichar **src, int len) = 0;
+ Converter() {
+ ilseq_repr = false;
+ }
+ virtual ~Converter() { }
+ void enable_ilseq_repr(bool val = true) {
+ ilseq_repr = val;
+ }
+};
+
+#ifdef USE_ICONV
+class IconvConverter : public Converter {
+ iconv_t cd;
+public:
+ IconvConverter();
+ virtual ~IconvConverter();
+
+ int set_source_encoding(const char *encoding);
+ int set_target_encoding(const char *encoding);
+ virtual int convert(unichar **dest, char **src, int len);
+ virtual int convert(char **dest, unichar **src, int len);
+};
+#endif
+
+class ISO88598Converter : public Converter {
+public:
+ virtual int convert(unichar **dest, char **src, int len);
+ virtual int convert(char **dest, unichar **src, int len);
+};
+
+class Latin1Converter : public Converter {
+public:
+ virtual int convert(unichar **dest, char **src, int len);
+ virtual int convert(char **dest, unichar **src, int len);
+};
+
+class UTF8Converter : public Converter {
+public:
+ virtual int convert(unichar **dest, char **src, int len);
+ virtual int convert(char **dest, unichar **src, int len);
+};
+
+class ConverterFactory {
+ static Converter *get_internal_converter(const char *encoding);
+public:
+ static Converter *get_converter_from(const char *encoding);
+ static Converter *get_converter_to(const char *encoding);
+};
+
+#endif
+
diff --git a/dbg.cc b/dbg.cc
new file mode 100644
index 0000000..5557411
--- /dev/null
+++ b/dbg.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h> // exit()
+
+#include "terminal.h" // for the gettext stuff
+#include "dbg.h"
+
+static int debug_level = 0;
+
+void set_debug_level(int level)
+{
+ debug_level = level;
+}
+
+bool print_debug_level(int level)
+{
+ return level <= debug_level;
+}
+
+void debug_print(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+// I've just noticed that my fatal() function is very similar to fribidi's
+// die(). Strange. I've taken the variable names from an example in K&R, and
+// used the output of 'ls' ("try ... for more info"). Perhaps it proves
+// that two monkeys sitting at two keyboards and not typing randomly can
+// produce the same program :-)
+
+void fatal(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (fmt) {
+ fprintf(stderr, PACKAGE ": ");
+ vfprintf(stderr, fmt, ap);
+ }
+ fprintf(stderr, _("Try `%s --help' for more information.\n"), PACKAGE);
+ va_end(ap);
+ exit(1);
+}
+
diff --git a/dbg.h b/dbg.h
new file mode 100644
index 0000000..88a2ff0
--- /dev/null
+++ b/dbg.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_DBG_H
+#define BDE_DBG_H
+
+#include <config.h>
+
+void set_debug_level(int level);
+bool print_debug_level(int level);
+void debug_print(const char *fmt, ...);
+void fatal(const char *fmt, ...);
+
+#ifdef DEBUG
+# define DBG(level, args) \
+ do { \
+ if (print_debug_level(level)) \
+ debug_print args; \
+ } while (0)
+#else
+# define DBG(level, args) \
+ do { ; } while (0)
+#endif
+
+#endif
+
diff --git a/dialogline.cc b/dialogline.cc
new file mode 100644
index 0000000..35d501a
--- /dev/null
+++ b/dialogline.cc
@@ -0,0 +1,178 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <stdarg.h>
+
+#include "dialogline.h"
+#include "editor.h"
+#include "inputline.h"
+#include "question.h"
+#include "dbg.h"
+
+DialogLine::DialogLine(Editor *aApp)
+{
+ app = aApp;
+ modal_widget = NULL;
+ transient = false;
+}
+
+void DialogLine::resize(int lines, int columns, int y, int x)
+{
+ Label::resize(lines, columns, y, x);
+ if (modal_widget)
+ modal_widget->resize(lines, columns, y, x);
+}
+
+void DialogLine::show_message(const char *msg)
+{
+ set_text(msg);
+ transient = true;
+}
+
+void DialogLine::show_error_message(const char *msg)
+{
+ set_text(msg);
+ transient = false;
+}
+
+void DialogLine::show_message_fmt(const char *fmt, ...)
+{
+ u8string msg;
+ va_list ap;
+ va_start(ap, fmt);
+ msg.vcformat(fmt, ap);
+ va_end(ap);
+ show_message(msg.c_str());
+}
+
+INTERACTIVE void DialogLine::layout_windows()
+{
+ app->layout_windows();
+ // move the cursor back to the modal widget
+ if (modal_widget)
+ modal_widget->invalidate_view();
+}
+
+INTERACTIVE void DialogLine::refresh()
+{
+ app->refresh();
+ // move the cursor back to the modal widget
+ if (modal_widget)
+ modal_widget->invalidate_view();
+}
+
+void DialogLine::clear_transient_message()
+{
+ if (!empty() && transient)
+ set_text("");
+}
+
+INTERACTIVE void DialogLine::cancel_modal()
+{
+ modal_canceled = true;
+}
+
+// modalize() - does event pumping on some interactive widget.
+
+void DialogLine::modalize(Widget *wgt)
+{
+ modal_widget = wgt;
+ wgt->resize(window_height(), window_width(), window_begy(), window_begx());
+ modal_canceled = false;
+ while (wgt->is_modal() && !modal_canceled) {
+ Event evt;
+ wgt->update();
+ doupdate();
+ get_next_event(evt, wgt->wnd);
+ if (!handle_event(evt))
+ wgt->handle_event(evt);
+ }
+ modal_widget = NULL;
+ show_message(modal_canceled ? _("Canceled") : "");
+}
+
+bool DialogLine::ask_yes_or_no(const char *msg, bool *canceled)
+{
+ Question ipt(msg);
+ modalize(&ipt);
+ if (canceled)
+ *canceled = modal_canceled;
+ return modal_canceled ? false : ipt.get_answer();
+}
+
+int DialogLine::get_number(const char *msg, int default_num, bool *canceled)
+{
+ u8string cstrnum;
+ cstrnum.cformat("%d", default_num);
+ unistring strnum;
+ strnum.init_from_utf8(cstrnum.c_str());
+ InputLine ipt(msg, strnum);
+ modalize(&ipt);
+ if (canceled)
+ *canceled = modal_canceled;
+ if (modal_canceled) {
+ return 0;
+ } else {
+ cstrnum.init_from_unichars(ipt.get_text());
+ return atoi(cstrnum.c_str());
+ }
+}
+
+unistring DialogLine::query(const char *msg, const char *default_text,
+ int history_set, InputLine::CompleteType complete,
+ bool *alt_kbd)
+{
+ unistring text;
+ text.init_from_utf8(default_text);
+ return query(msg, text, history_set, complete, alt_kbd);
+}
+
+unistring DialogLine::query(const char *msg, const unistring &default_text,
+ int history_set, InputLine::CompleteType complete,
+ bool *alt_kbd)
+{
+ InputLine ipt(msg, default_text, history_set, complete);
+ if (alt_kbd)
+ ipt.set_alt_kbd(*alt_kbd);
+ modalize(&ipt);
+ if (alt_kbd)
+ *alt_kbd = ipt.get_alt_kbd();
+ return modal_canceled ? unistring() : ipt.get_text();
+}
+
+void DialogLine::update()
+{
+ if (modal_widget)
+ modal_widget->update();
+ else
+ Label::update();
+}
+
+// immediate_update() - called when we want to update the terminal
+// immediately, without having to wait for the event pump. As an intentional
+// side effect, the terminal cursor moves to this window.
+// TODO: move to the Widget class?
+
+void DialogLine::immediate_update()
+{
+ if (terminal::is_interactive()) {
+ update();
+ doupdate();
+ }
+}
+
diff --git a/dialogline.h b/dialogline.h
new file mode 100644
index 0000000..9bc904f
--- /dev/null
+++ b/dialogline.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_DIALOGLINE_H
+#define BDE_DIALOGLINE_H
+
+#include "label.h"
+#include "inputline.h"
+
+class Editor;
+
+// DialogLine does the dialog with the user: it displays informative
+// and error messages, asks for input and for confirmations.
+
+class DialogLine : public Label {
+
+ Editor *app;
+ Widget *modal_widget;
+ bool modal_canceled;
+
+ // A DialogLine can display two kinds of messages: transient and
+ // non-transient. Transient messages are displayed for a brief time,
+ // whereas non-transient messages are durable. The latter are used for
+ // error messages.
+ bool transient;
+
+ void modalize(Widget *wgt);
+
+public:
+
+ HAS_ACTIONS_MAP(DialogLine, Label);
+ HAS_BINDINGS_MAP(DialogLine, Label);
+
+ DialogLine(Editor *aApp);
+
+ void show_message(const char *msg);
+ void show_message_fmt(const char *fmt, ...);
+ void show_error_message(const char *msg);
+ void clear_transient_message();
+
+ unistring query(const char *msg, const char *default_text = NULL,
+ int history_set = 0,
+ InputLine::CompleteType complete = InputLine::cmpltOff,
+ bool *alt_kbd = NULL);
+ unistring query(const char *msg, const unistring &default_text,
+ int history_set = 0,
+ InputLine::CompleteType complete = InputLine::cmpltOff,
+ bool *alt_kbd = NULL);
+
+ int get_number(const char *msg, int default_num, bool *canceled = NULL);
+ bool ask_yes_or_no(const char *msg, bool *canceled = NULL);
+
+ INTERACTIVE void cancel_modal();
+ INTERACTIVE void layout_windows();
+ INTERACTIVE void refresh();
+
+ virtual void update();
+ void immediate_update();
+ virtual void resize(int lines, int columns, int y, int x);
+};
+
+#endif
+
diff --git a/directvect.h b/directvect.h
new file mode 100644
index 0000000..b6af373
--- /dev/null
+++ b/directvect.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_SIMPLEVEC_H
+#define BDE_SIMPLEVEC_H
+
+#include <vector>
+
+// For some vectors we use our DirectVector instead of STL's vector.
+//
+// Why's that?
+//
+// In this application I use vector<T> mostly as an "enhanced" array.
+// Unfortunately, STL's vector<T>::iterator is not necessarily a pointer to
+// T. That's the case in newer STL libraries provided with GNU C++.
+//
+// DirectVector accepts T* arguments.
+
+template <class T>
+class DirectVector {
+ std::vector<T> vec;
+public:
+ typedef size_t size_type;
+
+ DirectVector(): vec() {}
+ DirectVector(size_type n): vec(n) {}
+ DirectVector(size_type n, const T& t): vec(n, t) {}
+ DirectVector(const DirectVector& other_vec): vec(other_vec.vec) {}
+ DirectVector(const T *first, const T *last): vec(first, last) {}
+ DirectVector& operator=(const DirectVector& other_vec) {
+ vec.operator=(other_vec.vec);
+ return *this;
+ }
+
+ size_type size() const { return vec.size(); }
+ size_type capacity() const { return vec.capacity(); }
+ bool empty() const { return vec.empty(); }
+ T* begin() { return &vec[0]; }
+ T* end() { return (begin() + size()); }
+ const T* begin() const { return &vec[0]; }
+ const T* end() const { return (begin() + size()); }
+ T& operator[] (size_type n) { return vec[n]; }
+ const T& operator[] (size_type n) const { return vec[n]; }
+
+ void reserve(size_type n) { vec.reserve(n); }
+ void resize(size_type n) { vec.resize(n); }
+ T& back() { return vec.back(); }
+ T& front() { return vec.front(); }
+ const T& front() const { return vec.front(); }
+ const T& back() const { return vec.back(); }
+
+ void push_back(const T& x) { vec.push_back(x); }
+ void pop_back() { vec.pop_back(); }
+
+ void swap(DirectVector& other_vec) { vec.swap(other_vec); }
+ void clear() { vec.clear(); }
+
+ // Note the "&*expression" syntax. "*" dereferences the iterator
+ // and "&" gives us the pointer we need.
+ T* insert(T* pos, const T& x) {
+ return &*vec.insert(vec.begin() + (pos - begin()), x);
+ }
+ void insert(T* pos, const T* first, const T* last) {
+ vec.insert(vec.begin() + (pos - begin()), first, last);
+ }
+ void insert(T* pos, size_type n, const T& x) {
+ vec.insert(vec.begin() + (pos - begin()), n, x);
+ }
+ T* erase(T* pos) {
+ return &*vec.erase(vec.begin() + (pos - begin()));
+ }
+ T* erase(T* first, T* last) {
+ return &*vec.erase(vec.begin() + (first - begin()),
+ vec.begin() + (last - begin()));
+ }
+
+ bool operator==(const DirectVector &other) const { return vec == other.vec; }
+ bool operator!=(const DirectVector &other) const { return vec != other.vec; }
+ bool operator<(const DirectVector &other) const { return vec < other.vec; }
+};
+
+#endif
+
diff --git a/dispatcher.h b/dispatcher.h
new file mode 100644
index 0000000..74bbfc3
--- /dev/null
+++ b/dispatcher.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_DISPATCHER_H
+#define BDE_DISPATCHER_H
+
+#include "event.h"
+
+// class Dispatcher represents a class that receives GUI events.
+
+#define INTERACTIVE
+
+struct binding_entry {
+ Event evt;
+ const char *action;
+};
+
+class Dispatcher {
+
+public:
+
+ Dispatcher() { }
+
+ virtual bool do_action(const char *action) {
+ return false;
+ }
+
+ virtual const char *get_event_action(const Event &evt) {
+ return NULL;
+ }
+
+ virtual bool get_action_event(const char *action, Event &evt) {
+ return false;
+ }
+
+ virtual const char *get_action_description(const char *action) {
+ return NULL;
+ }
+
+ virtual bool handle_event(const Event &evt) {
+ return do_action(get_event_action(evt));
+ }
+};
+
+// :TODO: I should use STL's map instead of a simple linear search.
+
+#define HAS_ACTIONS_MAP(CLASS, PARENT_CLASS) \
+ typedef void (CLASS::*method_ptr)(); \
+ struct action_entry { \
+ const char *action; \
+ method_ptr method; \
+ const char *short_description; \
+ }; \
+ static action_entry actions_table[]; \
+ virtual bool do_action(const char *action) { \
+ if (!action) return false; \
+ action_entry *entry = actions_table; \
+ while (entry->action) { \
+ if (!strcmp(entry->action, action)) { \
+ (this->*(entry->method))(); \
+ return true; \
+ } \
+ entry++; \
+ } \
+ return PARENT_CLASS::do_action(action); \
+ } \
+ virtual const char *get_action_description \
+ (const char *action) { \
+ if (!action) return NULL; \
+ action_entry *entry = actions_table; \
+ while (entry->action) { \
+ if (!strcmp(entry->action, action)) \
+ return entry->short_description; \
+ entry++; \
+ } \
+ return PARENT_CLASS:: \
+ get_action_description(action); \
+ }
+
+#define ADD_ACTION(CLASS, METHOD, DESC) \
+ { #METHOD, &CLASS::METHOD, DESC }
+
+#define END_ACTIONS \
+ { 0, 0 }
+
+#define HAS_BINDINGS_MAP(CLASS, PARENT_CLASS) \
+ static binding_entry bindings_table[]; \
+ virtual const char *get_event_action(const Event &evt) { \
+ binding_entry *entry = bindings_table; \
+ while (entry->action) { \
+ if (entry->evt == evt) \
+ return entry->action; \
+ entry++; \
+ } \
+ return PARENT_CLASS::get_event_action(evt); \
+ } \
+ virtual bool get_action_event(const char *action, Event &evt) { \
+ binding_entry *entry = bindings_table; \
+ while (entry->action) { \
+ if (!strcmp(entry->action, action)) { \
+ evt = entry->evt; \
+ return true; \
+ } \
+ entry++; \
+ } \
+ return PARENT_CLASS::get_action_event(action, evt); \
+ }
+
+#define END_BINDINGS \
+ { Event(), 0 }
+
+#endif
+
diff --git a/editbox.cc b/editbox.cc
new file mode 100644
index 0000000..e298049
--- /dev/null
+++ b/editbox.cc
@@ -0,0 +1,1909 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <algorithm>
+
+#include "editbox.h"
+#include "scrollbar.h"
+#include "transtbl.h"
+#include "univalues.h"
+#include "themes.h"
+#include "dbg.h"
+
+TranslationTable EditBox::transtbl;
+TranslationTable EditBox::reprtbl;
+TranslationTable EditBox::altkbdtbl;
+
+// DOS's EOP is actually two characters: CR + LF, but we represent it
+// internally as one character to make processing much simpler. The value we
+// choose for it is in Unicode's Private Area block.
+
+#define DOS_PS 0xF880
+#define MAC_PS 0x0D
+#define UNIX_PS 0x0A
+
+// Initialization {{{
+
+EditBox::EditBox()
+{
+ create_window();
+
+ status_listener = NULL;
+ error_listener = NULL;
+ margin_before = 0;
+ margin_after = 1;
+ primary_mark.para = -1;
+ optimal_vis_column = -1;
+ bidi_enabled = true;
+ visual_cursor_movement = false;
+ modified = false;
+ scroll_step = 4;
+ update_region = rgnAll;
+ alt_kbd = false;
+ auto_justify = false;
+ justification_column = 72;
+ auto_indent = false;
+ translation_mode = false;
+ read_only = false;
+ smart_typing = false;
+ wrap_type = wrpAtWhiteSpace;
+ dir_algo = algoContextRTL;
+ tab_width = 8;
+ show_tabs = true;
+ show_explicits = true;
+ show_paragraph_endings = true;
+ rtl_nsm_display = rtlnsmTransliterated;
+ maqaf_display = mqfAsis;
+ syn_hlt = synhltOff;
+ underline_hlt = false;
+ rfc2646_trailing_space = true;
+ data_transfer.in_transfer = false;
+ old_width = -1;
+ prev_command_type = current_command_type = cmdtpUnknown;
+ paragraphs.push_back(new Paragraph());
+}
+
+EditBox::~EditBox()
+{
+ for (int i = 0; i < parags_count(); i++)
+ delete paragraphs[i];
+}
+
+void EditBox::new_document()
+{
+ for (int i = 0; i < parags_count(); i++)
+ delete paragraphs[i];
+ paragraphs.clear();
+ paragraphs.push_back(new Paragraph());
+
+ undo_stack.clear();
+ unset_primary_mark();
+ set_modified(false);
+ cursor.zero();
+ scroll_to_cursor_line();
+ request_update(rgnAll);
+}
+
+void EditBox::sync_scrollbar(Scrollbar *scrollbar)
+{
+ scrollbar->set_total_size(parags_count());
+ scrollbar->set_page_size(window_height());
+ scrollbar->set_page_pos(top_line.para);
+}
+
+// }}}
+
+// Utility methods dealing with End-Of-Paragraphs {{{
+
+static inline bool is_eop(unichar ch) {
+ return (ch == DOS_PS || ch == MAC_PS || ch == UNIX_PS || ch == UNICODE_PS);
+}
+
+static inline eop_t get_eop_type(unichar ch)
+{
+ switch (ch) {
+ case DOS_PS: return eopDOS;
+ case MAC_PS: return eopMac;
+ case UNIX_PS: return eopUnix;
+ case UNICODE_PS: return eopUnicode;
+ default: return eopNone;
+ }
+}
+
+unichar EditBox::get_eop_char(eop_t eop)
+{
+ switch (eop) {
+ case eopDOS: return DOS_PS;
+ case eopMac: return MAC_PS;
+ case eopUnix: return UNIX_PS;
+ case eopUnicode: return UNICODE_PS;
+ default: return UNIX_PS; // we shouldn't arrive here
+ }
+}
+
+// get_curr_eop_char() returns the character corresponding to the current
+// paragraph's EOP.
+//
+// When creating new paragraphs, e.g. as a reslt of pressing ENTER or of
+// justifying lines, we copy the EOP of the current paragraph to the new
+// paragraphs. Since our document may contain differenet EOPs (for different
+// paragraphs), this seems to be the most sensible solution.
+//
+// When the current paragraph has no EOP (when it's the last in the buffer),
+// we look at the previous one. As a last resort we use Unix's EOP.
+
+unichar EditBox::get_curr_eop_char()
+{
+ if (curr_para()->eop == eopNone) {
+ if (cursor.para > 0)
+ return get_eop_char(paragraphs[cursor.para - 1]->eop);
+ else
+ return UNIX_PS; // default
+ } else {
+ return get_eop_char(curr_para()->eop);
+ }
+}
+
+// toggle_eops() is an interactive command to change all EOPs in the buffer.
+//
+// It'd be especially appreciated by Unix users who would like to convert
+// DOS line ends to Unix line ends, but it was written to toggle among all
+// possible EOPs.
+
+void EditBox::set_eops(eop_t new_eop)
+{
+ if (read_only) {
+ NOTIFY_ERROR(read_only);
+ return;
+ }
+
+ for (int i = 0; i < parags_count() - 1; i++)
+ paragraphs[i]->eop = new_eop;
+ set_modified(true);
+
+ request_update(rgnAll);
+}
+
+INTERACTIVE void EditBox::toggle_eops()
+{
+ eop_t new_eop = eopNone; // silence the compiler
+ switch (paragraphs[0]->eop) {
+ case eopUnix: new_eop = eopDOS; break;
+ case eopDOS: new_eop = eopMac; break;
+ case eopMac: new_eop = eopUnicode; break;
+ case eopUnicode: new_eop = eopUnix; break;
+ case eopNone: return; // no eops in document
+ }
+ set_eops(new_eop);
+}
+
+// }}}
+
+// Setters / Getters / Togglers {{{
+
+// The following are trivial set_xxx methods.
+
+void EditBox::set_read_only(bool value) {
+ read_only = value;
+ NOTIFY_CHANGE(read_only);
+}
+
+void EditBox::set_smart_typing(bool val) {
+ smart_typing = val;
+ NOTIFY_CHANGE(smart_typing);
+}
+
+void EditBox::set_modified(bool value) {
+ modified = value;
+ NOTIFY_CHANGE(modification);
+}
+
+void EditBox::set_auto_indent(bool value) {
+ auto_indent = value;
+ NOTIFY_CHANGE(auto_indent);
+}
+
+void EditBox::set_auto_justify(bool value) {
+ auto_justify = value;
+ NOTIFY_CHANGE(auto_justify);
+}
+
+void EditBox::set_justification_column(int value) {
+ if (value > 0)
+ justification_column = value;
+}
+
+void EditBox::set_scroll_step(int value) {
+ if (value > 0)
+ scroll_step = value;
+}
+
+void EditBox::set_primary_mark(const Point &point) {
+ primary_mark = point;
+ NOTIFY_CHANGE(selection);
+}
+
+void EditBox::unset_primary_mark() {
+ if (is_primary_mark_set()) {
+ primary_mark.para = -1;
+ request_update(rgnAll);
+ NOTIFY_CHANGE(selection);
+ }
+}
+
+INTERACTIVE void EditBox::toggle_primary_mark() {
+ if (is_primary_mark_set())
+ unset_primary_mark();
+ else
+ set_primary_mark();
+}
+
+// }}}
+
+// Misc {{{
+
+// calc_distance() - count the number of characters, counting EOP as 1,
+// between two points.
+
+int EditBox::calc_distance(Point p1, Point p2)
+{
+ int dist;
+ if (p2 < p1)
+ p2.swap(p1);
+
+ if (p1.para == p2.para) {
+ dist = p2.pos - p1.pos;
+ } else {
+ dist = paragraphs[p1.para]->str.len() - p1.pos + 1;
+ while (++p1.para < p2.para)
+ dist += paragraphs[p1.para]->str.len() + 1;
+ dist += p2.pos;
+ }
+ return dist;
+}
+
+// calc_inner_line() returns the inner-line on which the cursor stands.
+// In our terminology, "inner-line" is a paragraph screen-line, zero-based.
+
+int EditBox::calc_inner_line()
+{
+ for (int line_num = 0; line_num < curr_para()->breaks_count(); line_num++) {
+ idx_t line_break = curr_para()->line_breaks[line_num];
+ if (cursor.pos < line_break
+ || (cursor.pos == line_break
+ && cursor.pos == curr_para()->str.len()))
+ return line_num;
+ }
+ return 0;
+}
+
+// }}}
+
+// Clipboard {{{
+
+INTERACTIVE void EditBox::copy() {
+ cut_or_copy(true);
+}
+
+INTERACTIVE void EditBox::cut() {
+ cut_or_copy(false);
+}
+
+INTERACTIVE void EditBox::paste() {
+ insert_text(clipboard);
+}
+
+// cut_or_copy() -- cut and/or copies text into the clipboard. It does it by
+// calling copy_text() and delete_text().
+//
+// @param just_copy - don't delete the text.
+
+void EditBox::cut_or_copy(bool just_copy)
+{
+ if (is_primary_mark_set()) {
+ int len = calc_distance(cursor, primary_mark);
+ // copy_text() and delete_text() advance the cursor --
+ // that's why this code is a bit confusing: we restore the
+ // cursor position after each call.
+ Point orig_cursor = cursor;
+ if (cursor > primary_mark)
+ cursor = primary_mark;
+ if (just_copy) {
+ copy_text(len);
+ cursor = orig_cursor;
+ } else {
+ Point tmp = cursor;
+ copy_text(len);
+ cursor = tmp;
+ delete_text(len);
+ }
+ unset_primary_mark();
+ } else {
+ NOTIFY_ERROR(no_selection);
+ }
+}
+
+// copy_text() -- copies "len" characters to the clipboard. advances the
+// cursor.
+// if the 'append' parameter is true, the characters don't replace the
+// clipboard but are appended to it.
+
+void EditBox::copy_text(int len, bool append)
+{
+ if (!append)
+ clipboard.clear();
+ while (len > 0) {
+ if (cursor.pos == curr_para()->str.len()) {
+ if (cursor.para < parags_count() - 1) {
+ clipboard.push_back(get_curr_eop_char());
+ cursor.para++;
+ cursor.pos = 0;
+ }
+ len--;
+ } else {
+ int to_copy = MIN(curr_para()->str.len() - cursor.pos, len);
+ clipboard.append(curr_para()->str.begin() + cursor.pos, to_copy);
+ cursor.pos += to_copy;
+ len -= to_copy;
+ }
+ }
+}
+
+// }}}
+
+// Undo {{{
+
+INTERACTIVE void EditBox::undo()
+{
+ undo_op(undo_stack.get_prev_op());
+ // If we undo all the changes to the buffer, then we return it to its
+ // initial state. clear the "modified" flag.
+ if (!undo_stack.is_undo_available()
+ && !undo_stack.disabled() && !undo_stack.was_truncated())
+ set_modified(false);
+}
+
+INTERACTIVE void EditBox::redo()
+{
+ redo_op(undo_stack.get_next_op());
+}
+
+// undo_op() -- undoes en operation. For example, if the operation was to
+// delete some text then we re-insert the deleted text.
+//
+// There are three fundamental operations: inserting text, deleting text and
+// replacing text.
+
+void EditBox::undo_op(UndoOp *op)
+{
+ if (!op)
+ return;
+
+ switch (op->type) {
+ case opDelete:
+ cursor = op->point;
+ insert_text(op->deleted_text, true);
+ break;
+ case opInsert:
+ cursor = op->point;
+ delete_text(op->inserted_text.len(), true);
+ break;
+ case opReplace:
+ cursor = op->point;
+ replace_text(op->deleted_text, op->inserted_text.len(), true);
+ break;
+ }
+}
+
+void EditBox::redo_op(UndoOp *op)
+{
+ if (!op)
+ return;
+
+ switch (op->type) {
+ case opDelete:
+ cursor = op->point;
+ delete_text(op->deleted_text.len(), true);
+ break;
+ case opInsert:
+ cursor = op->point;
+ insert_text(op->inserted_text, true);
+ break;
+ case opReplace:
+ cursor = op->point;
+ replace_text(op->inserted_text, op->deleted_text.len(), true);
+ break;
+ }
+}
+
+INTERACTIVE void EditBox::toggle_key_for_key_undo()
+{
+ set_key_for_key_undo(!is_key_for_key_undo());
+}
+
+// }}}
+
+// Data Transfer {{{
+
+// start_data_transfer() - must be called to start any data transfer; it
+// initializes the data tranfser state (the "data_transfer" struct).
+//
+// @param dir - the direction in which the data is transferred. Either into
+// EditBox (dataTransferIn) or out of EditBox (dataTransferOut).
+//
+// @param new_doc - clear the buffer before transferring data into EditBox.
+//
+// @param selection_only - when transferring data out, transfer only the
+// selection, not the whole buffer.
+
+void EditBox::start_data_transfer(data_transfer_direction dir,
+ bool new_doc,
+ bool selection_only)
+{
+ data_transfer.dir = dir;
+ data_transfer.in_transfer = true;
+
+ if (dir == dataTransferIn) {
+ // Loading a file
+ if (new_doc) {
+ // "Open new file"
+ new_document();
+ data_transfer.skip_undo = true;
+ data_transfer.clear_modified_flag = true;
+ } else {
+ // "Insert file"
+ data_transfer.skip_undo = false;
+ data_transfer.clear_modified_flag = false;
+ }
+ data_transfer.prev_is_cr = false;
+ data_transfer.cursor_origin = cursor;
+ } else {
+ // Saving a file
+ data_transfer.cursor_origin = cursor;
+ if (selection_only && is_primary_mark_set()) {
+ data_transfer.ntransferred_out = 0;
+ data_transfer.ntransferred_out_max = calc_distance(cursor, primary_mark);
+ if (cursor > primary_mark)
+ cursor = primary_mark;
+ } else {
+ data_transfer.ntransferred_out_max = -1;
+ cursor.zero();
+ }
+ data_transfer.at_eof = false;
+ // An error may occur during saving, so only a an object that knows
+ // the IO result may clear the modification flag.
+ data_transfer.clear_modified_flag = false;
+ }
+}
+
+bool EditBox::is_in_data_transfer() const {
+ return data_transfer.in_transfer;
+}
+
+int EditBox::transfer_data(unichar *buf, int len)
+{
+ if (data_transfer.dir == dataTransferIn)
+ return transfer_data_in(buf, len);
+ else
+ return transfer_data_out(buf, len);
+}
+
+// transfer_data_out() - transfers data out of EditBox. returns the number of
+// characters placed in "buf". when it returns 0, we know we've reached end
+// of buffer.
+
+int EditBox::transfer_data_out(unichar *buf, int len)
+{
+ if (data_transfer.at_eof)
+ return 0; // end-of-buffer: no more chars to write
+
+ int nwritten = 0; // number of chars we've written to buf
+ while (nwritten < len && !data_transfer.at_eof) {
+ if (cursor.pos == curr_para()->str.len()) {
+
+ // write EOP
+ if (curr_para()->eop != eopNone) {
+ if (curr_para()->eop == eopDOS) {
+ // DOS's EOP is two characters. If there's not
+ // enough space in buf, we exit and do it in the
+ // next call.
+ if (len - nwritten < 2)
+ break;
+ *buf++ = '\r';
+ *buf++ = '\n';
+ nwritten += 2;
+ } else {
+ *buf++ = get_curr_eop_char();
+ nwritten++;
+ }
+ data_transfer.ntransferred_out++;
+ if (data_transfer.ntransferred_out_max != -1
+ && data_transfer.ntransferred_out
+ >= data_transfer.ntransferred_out_max)
+ data_transfer.at_eof = true;
+ }
+
+ if (cursor.para < parags_count() - 1) {
+ // advance to next paragraph
+ cursor.para++;
+ cursor.pos = 0;
+ } else {
+ // we've reached end of buffer
+ data_transfer.at_eof = true;
+ }
+ } else {
+ // write the [cursor, end-of-paragraph) segment into buf.
+ int to_copy = MIN(curr_para()->str.len() - cursor.pos, (len - nwritten));
+ if (data_transfer.ntransferred_out_max != -1
+ && data_transfer.ntransferred_out + to_copy
+ > data_transfer.ntransferred_out_max) {
+ to_copy = MAX(data_transfer.ntransferred_out_max
+ - data_transfer.ntransferred_out, 0);
+ }
+ unichar *src = curr_para()->str.begin() + cursor.pos;
+ cursor.pos += to_copy;
+ nwritten += to_copy;
+ data_transfer.ntransferred_out += to_copy;
+ while (to_copy--)
+ *buf++ = *src++;
+ if (data_transfer.ntransferred_out_max != -1
+ && data_transfer.ntransferred_out
+ >= data_transfer.ntransferred_out_max)
+ data_transfer.at_eof = true;
+ }
+ }
+ return nwritten;
+}
+
+#define INSERT_DOS_PS() \
+ do { \
+ unichar ch = DOS_PS; \
+ insert_text(&ch, 1, data_transfer.skip_undo); \
+ } while (0)
+#define INSERT_CR() \
+ do { \
+ unichar ch = '\r'; \
+ insert_text(&ch, 1, data_transfer.skip_undo); \
+ } while (0)
+
+// transfer_data_in() - transfers data into EditBox.
+
+int EditBox::transfer_data_in(unichar *data, int len)
+{
+ // What makes the implementation complicated is that we need to convert
+ // DOS's EOP (CR + LF) to one pseudo character. Since these two characters
+ // may be passed to this method in two separate calls, we have to maintain
+ // state (prev_is_cr = "last character was CR").
+ int start = 0;
+ if (data_transfer.prev_is_cr && len) {
+ if (data[0] == '\n') {
+ INSERT_DOS_PS();
+ start = 1;
+ } else {
+ INSERT_CR();
+ }
+ data_transfer.prev_is_cr = false;
+ }
+ for (int i = start; i < len; i++) {
+ if (data[i] == '\r') {
+ insert_text(data + start, i - start, data_transfer.skip_undo);
+ start = i;
+ if (i + 1 < len) {
+ if (data[i + 1] == '\n') {
+ INSERT_DOS_PS();
+ start += 2;
+ i++;
+ }
+ } else {
+ data_transfer.prev_is_cr = true;
+ start++;
+ }
+ }
+ }
+ insert_text(data + start, len - start, data_transfer.skip_undo);
+ return 0; // return value is meaningless
+}
+
+// end_data_transfer() -- must be called when transfer is completed.
+
+void EditBox::end_data_transfer()
+{
+ if (data_transfer.dir == dataTransferIn) {
+ if (data_transfer.prev_is_cr)
+ INSERT_CR();
+ }
+
+ if (data_transfer.clear_modified_flag)
+ set_modified(false);
+ data_transfer.in_transfer = false;
+
+ cursor = data_transfer.cursor_origin;
+ if (data_transfer.dir == dataTransferIn)
+ last_modification = cursor;
+ scroll_to_cursor_line();
+ request_update(rgnAll);
+}
+
+#undef INSERT_DOS_PS
+#undef INSERT_CR
+
+// }}}
+
+// Scrolling {{{
+
+// add_rows_to_line() - adds screen lines to a CombinedLine. For example, if
+// we advance the cursor forward by one screen line, we could find the new
+// line using:
+//
+// CombinedLine line(cursor.para, calc_inner_line());
+// add_rows_to_line(line, 1);
+//
+// @params rows - screen rows to add. can be negative.
+
+void EditBox::add_rows_to_line(CombinedLine &combline, int rows)
+{
+ if (rows > 0) {
+ int remaining_line_breaks = paragraphs[combline.para]->breaks_count()
+ - combline.inner_line - 1;
+ if (rows <= remaining_line_breaks) {
+ // Our final position is inside the paragraph.
+ // We zero rows to finish the loop.
+ combline.inner_line += rows;
+ rows = 0;
+ } else {
+ if (combline.para < parags_count() - 1) {
+ // We have way to go. Move to the next paragraph,
+ // decrease rows, and let the loop handle the rest.
+ combline.para++;
+ combline.inner_line = 0;
+ rows -= remaining_line_breaks + 1;
+ } else {
+ // We are at the last paragraph. Move to the last line.
+ combline.inner_line = paragraphs[combline.para]->breaks_count() - 1;
+ rows = 0;
+ }
+ }
+
+ if (rows != 0) // way to go?
+ add_rows_to_line(combline, rows);
+ }
+
+ if (rows < 0) {
+ if (-rows <= combline.inner_line) {
+ // Our final position is inside the paragraph.
+ // We zero rows to finish the loop.
+ combline.inner_line -= -rows;
+ rows = 0;
+ } else {
+ if (combline.para > 0) {
+ // We have way to go. Move to the previous paragraph, increase
+ // rows (towards zero), and let the loop handle the rest.
+ combline.para--;
+ rows += combline.inner_line + 1;
+ combline.inner_line = paragraphs[combline.para]->breaks_count() - 1;
+ } else {
+ // We are at the first paragraph. Move to the first line.
+ combline.inner_line = 0;
+ rows = 0;
+ }
+ }
+
+ if (rows != 0) // way to go?
+ add_rows_to_line(combline, rows);
+ }
+}
+
+// lines_diff() - calculates the difference, in screen lines, between
+// two CombinedLines.
+
+int EditBox::lines_diff(CombinedLine L1, CombinedLine L2)
+{
+ if (L2 < L1)
+ L2.swap(L1);
+
+ if (L1.para == L2.para)
+ return L2.inner_line - L1.inner_line;
+
+ int rows = paragraphs[L1.para]->breaks_count() - L1.inner_line;
+ L1.para++;
+ while (L1.para < L2.para) {
+ rows += paragraphs[L1.para]->breaks_count();
+ L1.para++;
+ }
+ rows += L2.inner_line;
+
+ return rows;
+}
+
+// scroll_to_cursor_line() - makes sure the cursor line (=current line) is
+// visible on screen. If not, it scrolls. It is called at the end of various
+// movement commands.
+
+void EditBox::scroll_to_cursor_line()
+{
+ CombinedLine curr(cursor.para, calc_inner_line());
+
+ if (curr < top_line) {
+ // if the current line is smaller than top_line, it means it's outside
+ // the window view and we have to scroll up to make it visible.
+
+ // calculate the new top line
+ CombinedLine old_top_line = top_line;
+ top_line = curr;
+ add_rows_to_line(top_line, -(get_effective_scroll_step() - 1));
+
+ // Performance: we can either repaint all the window (rgnAll),
+ // or use curses' wscrl() and paint only the lines that aren't already
+ // on screen.
+ if (top_line.is_near(old_top_line)) {
+ int diff = lines_diff(top_line, old_top_line);
+ scrollok(wnd, TRUE);
+ wscrl(wnd, -diff);
+ scrollok(wnd, FALSE);
+ request_update(top_line.para, old_top_line.para - 1);
+ } else {
+ request_update(rgnAll);
+ }
+
+ return;
+ }
+
+ // We don't need to scroll up. Now we check whether we need to scroll down.
+
+ // calculate the bottom line
+ CombinedLine bottom_line = top_line;
+ add_rows_to_line(bottom_line, window_height() - 1);
+
+ if (curr > bottom_line) {
+ // if the current line is past bottom_line, we scroll down to make
+ // it visible.
+
+ // calculate the new bottom line
+ bottom_line = curr;
+ add_rows_to_line(bottom_line, get_effective_scroll_step() - 1);
+
+ // and the new top line
+ CombinedLine old_top_line = top_line;
+ top_line = bottom_line;
+ add_rows_to_line(top_line, -(window_height() - 1));
+
+ // Performance: either repaint everything or just the lines that
+ // aren't already on screen.
+ if (top_line.is_near(old_top_line)) {
+ int diff = lines_diff(top_line, old_top_line);
+ CombinedLine from = bottom_line;
+ add_rows_to_line(from, -(diff - 1));
+ scrollok(wnd, TRUE);
+ wscrl(wnd, diff);
+ scrollok(wnd, FALSE);
+ request_update(from.para, bottom_line.para);
+ } else {
+ request_update(rgnAll);
+ }
+ }
+}
+
+// move_forward_page() - interactive command called in response to the
+// "Page Down" key. It mimics emacs.
+
+INTERACTIVE void EditBox::move_forward_page()
+{
+ // First, check whether the end of the buffer is already on screen (by
+ // calculating the bottom line). If so, move to the last line and quit.
+
+ CombinedLine bottom_line = top_line;
+ add_rows_to_line(bottom_line, window_height() - 1);
+
+ if (lines_diff(top_line, bottom_line) < window_height() - 1) {
+ move_relative_line(window_height());
+ return;
+ }
+
+ // No, the end of the buffer is not on screen. Advance top_line by
+ // window_height() lines (but keep 2 lines of context).
+ add_rows_to_line(top_line, window_height() - 2);
+
+ // if the cursor is now outside the view ...
+ if (CombinedLine(cursor.para, calc_inner_line()) < top_line) {
+ // ... then move it to the top line.
+ cursor.para = top_line.para;
+ cursor.pos = (top_line.inner_line > 0)
+ ? curr_para()->line_breaks[top_line.inner_line - 1]
+ : 0;
+ invalidate_optimal_vis_column();
+ }
+
+ post_vertical_movement();
+ request_update(rgnAll);
+}
+
+// move_backward_page() - interactive command called in response to the
+// "Page Up" key. It mimics emacs.
+
+INTERACTIVE void EditBox::move_backward_page()
+{
+ // First, check whether the first line of the buffer if already shown. If
+ // so, move to that first line and quit.
+
+ if (top_line.para == 0 && top_line.inner_line == 0) {
+ move_relative_line(-window_height());
+ return;
+ }
+
+ // No, we can go back. Substract window_height() lines (minus 2 lines of
+ // context) from top_line.
+ add_rows_to_line(top_line, -(window_height() - 2));
+
+ // Calculate the new bottom line
+ CombinedLine bottom_line = top_line;
+ add_rows_to_line(bottom_line, window_height() - 1);
+
+ // if the cursor is now past the bottom line ...
+ if (CombinedLine(cursor.para, calc_inner_line()) > bottom_line) {
+ // ... then move it to the bottom line.
+ cursor.para = bottom_line.para;
+ cursor.pos = (bottom_line.inner_line > 0)
+ ? curr_para()->line_breaks[bottom_line.inner_line - 1]
+ : 0;
+ invalidate_optimal_vis_column();
+ }
+
+ post_vertical_movement();
+ request_update(rgnAll);
+}
+
+// center_line() - interactive command used to center the current line.
+// (like emacs's C-l)
+
+INTERACTIVE void EditBox::center_line()
+{
+ // Calculate new top line
+ CombinedLine new_top_line(cursor.para, calc_inner_line());
+ add_rows_to_line(new_top_line, -(window_height() / 2));
+ top_line = new_top_line;
+ request_update(rgnAll);
+}
+
+// }}}
+
+// Movement commands {{{
+
+// post_horizontal_movement() -- should be called at the end of horizontal
+// movement commands. It makes sure the cursor is on screen and notify our
+// listeners.
+
+void EditBox::post_horizontal_movement()
+{
+ scroll_to_cursor_line();
+ request_update(rgnCursor);
+ invalidate_optimal_vis_column();
+ NOTIFY_CHANGE(position);
+}
+
+// post_vertical_movement() -- should be called at the end of vertical
+// movement commands.
+
+void EditBox::post_vertical_movement()
+{
+ scroll_to_cursor_line();
+ request_update(rgnCursor);
+ NOTIFY_CHANGE(position);
+}
+
+// move_forward_char() - interactive command to move to the next logical
+// character.
+
+INTERACTIVE void EditBox::move_forward_char()
+{
+ if (cursor.pos < curr_para()->str.len()) {
+ cursor.pos++;
+ } else if (cursor.para < parags_count() - 1) {
+ cursor.para++;
+ cursor.pos = 0;
+ }
+ post_horizontal_movement();
+}
+
+// move_backward_char() - interactive command to move the the previous logical
+// character.
+
+INTERACTIVE void EditBox::move_backward_char()
+{
+ if (cursor.pos > 0) {
+ cursor.pos--;
+ } else if (cursor.para > 0) {
+ cursor.para--;
+ cursor.pos = curr_para()->str.len();
+ }
+ post_horizontal_movement();
+}
+
+// key_left() - responds to the "left arrow" key.
+
+INTERACTIVE void EditBox::key_left()
+{
+ if (curr_para()->is_rtl()) {
+ if (visual_cursor_movement)
+ move_forward_visual_char();
+ else
+ move_forward_char();
+ } else {
+ if (visual_cursor_movement)
+ move_backward_visual_char();
+ else
+ move_backward_char();
+ }
+}
+
+// key_left() - responds to the "right arrow" key.
+
+INTERACTIVE void EditBox::key_right()
+{
+ if (curr_para()->is_rtl()) {
+ if (visual_cursor_movement)
+ move_backward_visual_char();
+ else
+ move_backward_char();
+ } else {
+ if (visual_cursor_movement)
+ move_forward_visual_char();
+ else
+ move_forward_char();
+ }
+}
+
+bool EditBox::is_at_end_of_buffer() {
+ return cursor.para == parags_count() - 1
+ && cursor.pos == curr_para()->str.len();
+}
+
+bool EditBox::is_at_beginning_of_buffer() {
+ return cursor.para == 0 && cursor.pos == 0;
+}
+
+unichar EditBox::get_current_char()
+{
+ if (cursor.pos < curr_para()->str.len())
+ return curr_para()->str[cursor.pos];
+ else
+ return get_curr_eop_char();
+}
+
+// move_forward_word() -- interactive command to move to the end of the
+// current or next word.
+
+INTERACTIVE void EditBox::move_forward_word()
+{
+ while (!BiDi::is_wordch(get_current_char()) && !is_at_end_of_buffer())
+ move_forward_char();
+ while (BiDi::is_wordch(get_current_char()) && !is_at_end_of_buffer())
+ move_forward_char();
+}
+
+bool EditBox::is_at_beginning_of_word()
+{
+ if (cursor.pos == curr_para()->str.len())
+ return false;
+ if (cursor.pos == 0)
+ return BiDi::is_wordch(curr_para()->str[0]);
+ return (BiDi::is_wordch(curr_para()->str[cursor.pos])
+ && !BiDi::is_wordch(curr_para()->str[cursor.pos-1]));
+}
+
+// move_backward_word() -- interactive command to move to the start of the
+// current or previous word.
+
+INTERACTIVE void EditBox::move_backward_word()
+{
+ if (is_at_beginning_of_word())
+ move_backward_char();
+ while (!is_at_beginning_of_word() && !is_at_beginning_of_buffer())
+ move_backward_char();
+}
+
+// move_beginning_of_buffer() -- interactive command to move to the beginning
+// of the buffer
+
+INTERACTIVE void EditBox::move_beginning_of_buffer()
+{
+ cursor.zero();
+ post_vertical_movement();
+}
+
+// move_end_of_buffer() -- interactive command to move to the end of the
+// buffer
+
+INTERACTIVE void EditBox::move_end_of_buffer()
+{
+ cursor.para = parags_count() - 1;
+ cursor.pos = 0;
+ post_vertical_movement();
+}
+
+// move_beginning_of_line() - interactive command to move to the beginning of
+// the current screen line.
+
+INTERACTIVE void EditBox::move_beginning_of_line()
+{
+ idx_t prev_line_break = 0;
+ for (int line_num = 0; line_num < curr_para()->breaks_count(); line_num++) {
+ idx_t line_break = curr_para()->line_breaks[line_num];
+ if (cursor.pos < line_break
+ || (cursor.pos == line_break
+ && cursor.pos == curr_para()->str.len())) {
+ cursor.pos = prev_line_break;
+ break;
+ }
+ prev_line_break = line_break;
+ }
+ post_horizontal_movement();
+}
+
+// move_end_of_line() - interactive command to move to the end of
+// the current screen line.
+
+INTERACTIVE void EditBox::move_end_of_line()
+{
+ for (int line_num = 0; line_num < curr_para()->breaks_count(); line_num++) {
+ idx_t line_break = curr_para()->line_breaks[line_num];
+ if (cursor.pos < line_break) {
+ cursor.pos = line_break;
+ if (line_num != curr_para()->breaks_count() - 1)
+ cursor.pos--;
+ break;
+ }
+ }
+ post_horizontal_movement();
+}
+
+INTERACTIVE void EditBox::move_last_modification()
+{
+ if (last_modification.para || last_modification.pos)
+ set_cursor_position(last_modification);
+}
+
+// set_cursor_position(Point) - allow other objects to position the cursor
+// at a specific paragraph and column.
+
+void EditBox::set_cursor_position(const Point &point)
+{
+ cursor.para = MIN(MAX(point.para, 0), parags_count() - 1);
+ cursor.pos = MIN(MAX(point.pos, 0), curr_para()->str.len());
+ post_horizontal_movement();
+}
+
+// move_absolute_line() -- jumps to a specific paragraph (used by the user when
+// he knows where he wants to jump to, i.e. when reading compiler error messages).
+
+void EditBox::move_absolute_line(int line)
+{
+ set_cursor_position(Point(line, 0));
+}
+
+// move_relative_line(diff) - moves the cursor diff lines up or down.
+
+void EditBox::move_relative_line(int diff)
+{
+ if (!valid_optimal_vis_column())
+ optimal_vis_column = calc_vis_column();
+
+ CombinedLine curr(cursor.para, calc_inner_line());
+ add_rows_to_line(curr, diff);
+ cursor.para = curr.para;
+ // move the cursor to the beginning of inner_line
+ cursor.pos = (curr.inner_line > 0)
+ ? curr_para()->line_breaks[curr.inner_line - 1]
+ : 0;
+ // reposition the cursor at the right column
+ move_to_vis_column(optimal_vis_column);
+ post_vertical_movement();
+}
+
+// move_next_line() - interactive command to move the cursor to the next
+// screen line (e.g. in response to "key down")
+
+INTERACTIVE void EditBox::move_next_line()
+{
+ move_relative_line(1);
+}
+
+// move_previous_line() - interactive command to move the cursor to the
+// previous screen line (e.g. in response to "key up")
+
+INTERACTIVE void EditBox::move_previous_line()
+{
+ move_relative_line(-1);
+}
+
+// search_forward() - a simple method for searching. It's not adequate for
+// most uses: it can't ignore non-spacing marks and there's no corresponding
+// method for searching backwards. I'll have to work on that.
+//
+// returns true if the string was found.
+
+bool EditBox::search_forward(unistring str)
+{
+ str = str.toupper_ascii();
+ const unichar *first, *last;
+ for (int i = cursor.para; i < parags_count(); i++) {
+ const unistring para = paragraphs[i]->str.toupper_ascii();
+ first = para.begin();
+ last = para.end();
+ if (i == cursor.para) {
+ first += cursor.pos;
+ // skip the character under the cursor
+ if (first != last)
+ first++;
+ }
+ const unichar *pos = std::search(first, last, str.begin(), str.end());
+ if (pos != last) {
+ cursor.pos = pos - para.begin();
+ cursor.para = i;
+ post_horizontal_movement();
+ return true;
+ }
+ }
+ return false;
+}
+
+// move_first_char(ch) - Moves to the first ch in the buffer. When saving the
+// buffer to a file the charset conversion may fail. This method is used to
+// position the cursor on the offending character.
+
+void EditBox::move_first_char(unichar ch)
+{
+ for (int i = 0; i < parags_count(); i++) {
+ Paragraph &para = *paragraphs[i];
+ idx_t len = para.str.len();
+ for (int pos = 0; pos <= len; pos++)
+ if (
+ (pos < len && para.str[pos] == ch) ||
+ (pos == len && para.eop != eopNone
+ && get_eop_char(para.eop) == ch)
+ )
+ {
+ cursor.para = i;
+ cursor.pos = pos;
+ post_horizontal_movement();
+ return;
+ }
+ }
+}
+
+// }}}
+
+// Visual movement commands {{{
+
+void EditBox::set_visual_cursor_movement(bool v)
+{
+ visual_cursor_movement = v;
+ NOTIFY_CHANGE(selection); // I don't have a special notification for this event.
+}
+
+INTERACTIVE void EditBox::toggle_visual_cursor_movement()
+{
+ set_visual_cursor_movement(!get_visual_cursor_movement());
+}
+
+bool EditBox::is_at_end_of_screen_line()
+{
+ idx_t end_pos = curr_para()->str.len();
+ for (int line_num = 0; line_num < curr_para()->breaks_count(); line_num++) {
+ idx_t line_break = curr_para()->line_breaks[line_num];
+ if (cursor.pos < line_break) {
+ end_pos = line_break;
+ if (line_num != curr_para()->breaks_count() - 1)
+ end_pos--;
+ break;
+ }
+ }
+ return cursor.pos == end_pos;
+}
+
+bool EditBox::is_at_first_screen_line()
+{
+ return cursor.para == 0 && calc_inner_line() == 0;
+}
+
+bool EditBox::is_at_last_screen_line()
+{
+ return (cursor.para == parags_count()-1)
+ && (calc_inner_line() == curr_para()->breaks_count()-1);
+}
+
+// move_forward_visual_char() - moves a char forward, visually.
+
+INTERACTIVE void EditBox::move_forward_visual_char()
+{
+ int vis_column = calc_vis_column();
+ if (is_at_end_of_screen_line()) {
+ // move to visual start of next screen line
+ if (!is_at_last_screen_line()) {
+ move_relative_line(1);
+ move_to_vis_column(0);
+ }
+ } else {
+ move_to_vis_column(vis_column+1);
+ if (calc_vis_column() == vis_column) {
+ // We're stuck on a tab or on a wide character.
+ // Ugly hack, sorry :-(
+ move_forward_char();
+ }
+ }
+ post_horizontal_movement();
+}
+
+// move_backward_visual_char() - moves a char backward, visually.
+
+INTERACTIVE void EditBox::move_backward_visual_char()
+{
+ int vis_column = calc_vis_column();
+ if (vis_column == 0 // we need to move up a line,
+ && !is_at_first_screen_line()) // but only if we're not
+ // already at start of buffer
+ {
+ move_relative_line(-1);
+ move_end_of_line();
+ } else {
+ move_to_vis_column(vis_column-1);
+ }
+ post_horizontal_movement();
+}
+
+INTERACTIVE void EditBox::move_beginning_of_visual_line()
+{
+ move_to_vis_column(0);
+ post_horizontal_movement();
+}
+
+INTERACTIVE void EditBox::key_home()
+{
+ if (visual_cursor_movement)
+ move_beginning_of_visual_line();
+ else
+ move_beginning_of_line();
+}
+
+// }}}
+
+// Typing related: justification / indentation / alt_kbd / translation {{{
+
+// insert_char() - inserts a character into the buffer. It assumes the
+// character was typed by the user, so it also handles auto_justify.
+// If you want to insert a character without this extra processing, use
+// insert_text() instead.
+
+void EditBox::insert_char(unichar ch)
+{
+ insert_text(&ch, 1);
+
+ if (auto_justify && cursor.pos > justification_column) {
+ // find previous whitespace
+ idx_t wspos = cursor.pos - 1;
+ while (wspos > 0 && !BiDi::is_space(curr_para()->str[wspos]))
+ --wspos;
+ if (wspos > 0) {
+ idx_t new_cursor_pos = cursor.pos - wspos - 1;
+ cursor.pos = wspos;
+ ch = get_curr_eop_char();
+ if (rfc2646_trailing_space) {
+ // keep the space at the end of the line
+ cursor.pos++;
+ insert_text(&ch, 1);
+ cursor.pos = new_cursor_pos;
+ } else {
+ // overwrite the space with a new-line
+ replace_text(&ch, 1, 1);
+ cursor.pos = new_cursor_pos;
+ }
+ scroll_to_cursor_line();
+ }
+ }
+
+ last_modification = cursor;
+}
+
+// key_enter() - called when the user hits Enter
+
+void EditBox::key_enter()
+{
+ if (auto_indent) {
+ unistring indent;
+ unistring &para = curr_para()->str;
+ indent.push_back(get_curr_eop_char());
+ for (idx_t i = 0; i < cursor.pos && BiDi::is_space(para[i]); i++)
+ indent.push_back(para[i]);
+ insert_text(indent);
+ } else {
+ insert_char(get_curr_eop_char());
+ }
+}
+
+// key_enter() - interactive command to insert maqaf into the buffer
+
+INTERACTIVE void EditBox::insert_maqaf()
+{
+ insert_char(UNI_HEB_MAQAF);
+}
+
+// key_dash() - called when the user types '-'
+
+void EditBox::key_dash()
+{
+ // if the previous [non-NSM] char is RTL and we're in smart_typing mode,
+ // insert maqaf.
+ if (smart_typing && cursor.pos > 0
+ && (BiDi::is_rtl(curr_para()->str[cursor.pos-1])
+ || (BiDi::is_nsm(curr_para()->str[cursor.pos-1])
+ && cursor.pos > 1
+ && BiDi::is_rtl(curr_para()->str[cursor.pos-2]))) )
+ insert_maqaf();
+ else
+ insert_char('-');
+}
+
+void EditBox::set_translation_mode(bool value)
+{
+ if (!transtbl.empty()) {
+ translation_mode = value;
+ NOTIFY_CHANGE(translation_mode);
+ } else {
+ NOTIFY_ERROR(no_translation_table);
+ }
+}
+
+void EditBox::set_alt_kbd(bool val)
+{
+ if (!altkbdtbl.empty()) {
+ alt_kbd = val;
+ NOTIFY_CHANGE(alt_kbd);
+ }
+}
+
+INTERACTIVE void EditBox::toggle_alt_kbd()
+{
+ if (!altkbdtbl.empty()) {
+ set_alt_kbd(!get_alt_kbd());
+ } else {
+ NOTIFY_ERROR(no_alt_kbd);
+ }
+}
+
+// handle_event() - deals with literal keys. Special keys (control & alt
+// combinations, function keys, arrows, etc) are dealt by the base class,
+// Dispatcher.
+//
+// When "tranlation mode" is active, it translates the character according to
+// the "transtbl" table. When "alt_kbd" is active -- according to the
+// "altkbdtbl" table.
+
+bool EditBox::handle_event(const Event &evt)
+{
+ current_command_type = cmdtpUnknown;
+ if (evt.is_literal()) {
+ unichar ch = evt.ch;
+ if (in_translation_mode()) {
+ transtbl.translate_char(ch);
+ set_translation_mode(false);
+ } else if (alt_kbd) {
+ altkbdtbl.translate_char(ch);
+ }
+ switch (ch) {
+ case 13: key_enter(); break;
+ case '-': key_dash(); break;
+ default:
+ insert_char(ch);
+ }
+ prev_command_type = current_command_type;
+ return true;
+ } else {
+ bool ret = Dispatcher::handle_event(evt);
+ prev_command_type = current_command_type;
+ return ret;
+ }
+}
+
+static bool is_empty_or_starts_with_space(const unistring &str) {
+ return (str.empty() || BiDi::is_space(str[0]));
+}
+
+static bool is_blank(const unistring &str) {
+ for (idx_t i = 0; i < str.len(); i++)
+ if (!BiDi::is_space(str[i]))
+ return false;
+ return true;
+}
+
+// justify() - interactive command to justify the current "paragraph". Here
+// "paragraph" means a bunch of Paragraphs (what a user thinks of as "lines")
+// separated by blank or indented Paragraphs.
+
+INTERACTIVE void EditBox::justify()
+{
+ // Step 1.
+ // Move to a non-blank line
+
+ while (cursor.para < parags_count() - 1
+ && is_blank(curr_para()->str))
+ move_next_line();
+
+ // Step 2.
+ // Find the min and max lines that constitute this paragraph.
+
+ int min_para, max_para;
+ min_para = max_para = cursor.para;
+ // paragraphs are separated by blank lines or indentations.
+ while (min_para > 0
+ && !is_empty_or_starts_with_space(paragraphs[min_para - 1]->str))
+ --min_para;
+ while (max_para < parags_count() - 1
+ && !is_empty_or_starts_with_space(paragraphs[max_para + 1]->str))
+ ++max_para;
+ // if we're an indented paragraph, include the indented line.
+ if (min_para > 0
+ && !is_empty_or_starts_with_space(paragraphs[min_para]->str)
+ && !is_blank(paragraphs[min_para - 1]->str))
+ --min_para;
+
+ // Step 3.
+ // Collect all the lines into 'text'
+
+ unistring text;
+ for (int i = min_para; i <= max_para; i++) {
+ if (!text.empty() && !BiDi::is_space(text.back()))
+ text.push_back(' ');
+ text.append(paragraphs[i]->str);
+ }
+
+ // Step 4.
+ // Justify 'text' into 'justified'
+
+ unistring justified;
+ while (!text.empty()) {
+ if (!justified.empty()) {
+ if (rfc2646_trailing_space)
+ justified.push_back(' ');
+ justified.push_back(get_curr_eop_char());
+ }
+ int wspos = justification_column;
+ if (wspos >= text.len()) {
+ justified.append(text);
+ text.clear();
+ } else {
+ while (wspos > 0 && !BiDi::is_space(text[wspos]))
+ --wspos;
+ if (wspos > 0) {
+ justified.append(text, wspos);
+ text.erase_head(wspos + 1);
+ } else {
+ justified.append(text, justification_column);
+ text.erase_head(justification_column);
+ }
+ }
+ }
+
+ // Step 5.
+ // Delete the unjustified lines from the buffer and insert
+ // the 'justified' string instead.
+
+ cursor.pos = 0;
+ cursor.para = min_para;
+ int delete_len = calc_distance(cursor,
+ Point(max_para, paragraphs[max_para]->str.len()));
+ replace_text(justified, delete_len);
+
+ // Move past this paragraph, so that the next "justify" command
+ // justifies the next paragraph and so on.
+ move_next_line();
+ move_beginning_of_line();
+}
+
+// }}}
+
+// Paragraph Base Direction {{{
+
+void EditBox::set_dir_algo(diralgo_t value)
+{
+ dir_algo = value;
+ for (int i = 0; i < parags_count(); i++)
+ paragraphs[i]->determine_base_dir(dir_algo);
+ if (dir_algo == algoContextStrong || dir_algo == algoContextRTL)
+ calc_contextual_dirs(0, parags_count() - 1, false);
+ request_update(rgnAll);
+ NOTIFY_CHANGE(dir_algo);
+}
+
+// toggle_dir_algo() - interactive command to toggle the directionality
+// algorithm used.
+
+INTERACTIVE void EditBox::toggle_dir_algo() {
+ switch (dir_algo) {
+ case algoUnicode: set_dir_algo(algoContextStrong); break;
+ case algoContextStrong: set_dir_algo(algoContextRTL); break;
+ case algoContextRTL: set_dir_algo(algoForceLTR); break;
+ case algoForceLTR: set_dir_algo(algoForceRTL); break;
+ case algoForceRTL: set_dir_algo(algoUnicode); break;
+ }
+}
+
+// calc_contextual_dirs() - this method implements the "contextual" algorithm.
+// Other algorithms are implemented in BiDi::determine_base_dir(), but the
+// BiDi class doesn't know about the context of the paragraph, so we have to
+// do most of the work in EditBox.
+//
+// The contextual algorithm has the following effect:
+//
+// 1. If there's an RTL character in the paragraph, set the base
+// direction to RTL;
+// 2. Else: if there's an LTR character in the paragraph, set the base
+// direction to LTR;
+// 3. Else: the paragraph is neutral; set the base direction to the
+// base direction of the previous non-neutral paragraph. If all
+// previous paragraphs are neuteral, set it to the base direction
+// of the next non-neutral paragraph. If all paragraphs are
+// neutral, set it to LTR.
+//
+// #1 and #2 are implemented in BiDi::determine_base_dir(). #3 is implemented
+// here.
+
+void EditBox::calc_contextual_dirs(int min_para, int max_para,
+ bool update_display)
+{
+ // find the previous strong paragraph
+ while (min_para > 0) {
+ min_para--;
+ if (paragraphs[min_para]->individual_base_dir != dirN)
+ break;
+ }
+ // find the next strong paragraph
+ while (max_para < parags_count() - 1) {
+ max_para++;
+ if (paragraphs[max_para]->individual_base_dir != dirN)
+ break;
+ }
+
+ // loop from min_para to max_para and whenever you find a neutral
+ // paragraph, set its base direction to the last strong direction you
+ // encountered (prev_strong).
+ direction_t prev_strong = dirN;
+ for (int i = min_para; i <= max_para; i++) {
+ Paragraph &p = *paragraphs[i];
+ if (p.individual_base_dir == dirN) {
+ set_contextual_dir(p, prev_strong, update_display);
+ } else {
+ set_contextual_dir(p, p.individual_base_dir, update_display);
+ prev_strong = p.individual_base_dir;
+ }
+ }
+
+ // Do the same but now loop backwards: from max_para to min_para.
+ prev_strong = dirN;
+ for (int i = max_para; i >= min_para; i--) {
+ Paragraph &p = *paragraphs[i];
+ if (p.contextual_base_dir == dirN) {
+ set_contextual_dir(p, prev_strong, update_display);
+ } else {
+ prev_strong = p.contextual_base_dir;
+ }
+ }
+
+ // If there're any neutral paragraphs left, set their base direction to
+ // LTR
+ for (int i = min_para; i <= max_para; i++) {
+ Paragraph &p = *paragraphs[i];
+ if (p.contextual_base_dir == dirN)
+ set_contextual_dir(p, dirLTR, update_display);
+ }
+}
+
+inline void EditBox::set_contextual_dir(Paragraph &p, direction_t dir,
+ bool update_display)
+{
+ if (!update_display)
+ p.contextual_base_dir = dir;
+ else {
+ if (p.contextual_base_dir != dir) {
+ // if only the current paragraph has changed its direction,
+ // we don't need to repaint all the window.
+ p.contextual_base_dir = dir;
+ if (&p == curr_para())
+ request_update(rgnCurrent);
+ else
+ request_update(rgnAll);
+ }
+ }
+}
+
+// }}}
+
+// Modification {{{
+
+// post_modification() - should be called at the end of any primitive command
+// that changes the buffer. It invalidates the selection, the optimal visual
+// column and notifies our listeners.
+
+void EditBox::post_modification()
+{
+ invalidate_optimal_vis_column();
+ last_modification = cursor;
+ cache.invalidate();
+ if (is_primary_mark_set())
+ unset_primary_mark();
+ if (!is_modified())
+ set_modified(true);
+ NOTIFY_CHANGE(position);
+}
+
+// post_para_modification() - should be called for every paragraph that has
+// been modified. It recalculates its base direcion and rewraps it.
+
+void EditBox::post_para_modification(Paragraph &p)
+{
+ p.determine_base_dir(dir_algo);
+ wrap_para(p);
+}
+
+// delete_forward_char() - interactive command to delete the character
+// we're on. Usually bound to the DELETE key.
+
+INTERACTIVE void EditBox::delete_forward_char()
+{
+ delete_text(1);
+}
+
+// delete_backward_char() - interactive command to delete the previous
+// character. Usually bound to the BACKSPACE key.
+
+INTERACTIVE void EditBox::delete_backward_char()
+{
+ if (!is_at_beginning_of_buffer()) {
+ move_backward_char();
+ delete_forward_char();
+ }
+}
+
+// cut_end_of_paragraph() - interactive command to cut till
+// end of paragraph. If the cursor is already at the end, it deletes
+// the EOP (ala emacs).
+
+INTERACTIVE void EditBox::cut_end_of_paragraph()
+{
+ int count;
+ if (cursor.pos == curr_para()->str.len())
+ count = 1;
+ else
+ count = curr_para()->str.len() - cursor.pos;
+
+ Point orig_cursor = cursor;
+ copy_text(count, (prev_command_type == cmdtpKill));
+ cursor = orig_cursor;
+ delete_text(count);
+
+ current_command_type = cmdtpKill;
+}
+
+// delete_paragraph() - interactive command to delete the current
+// paragraph.
+
+INTERACTIVE void EditBox::delete_paragraph()
+{
+ cursor.pos = 0;
+ delete_text(curr_para()->str.len() + 1);
+}
+
+// delete_forward_word() - interactive command to delete till the
+// end of the current or next word.
+
+INTERACTIVE void EditBox::delete_forward_word()
+{
+ Point start = cursor;
+ move_forward_word();
+ int len = calc_distance(cursor, start);
+ cursor = start;
+ delete_text(len);
+}
+
+// delete_backward_word() - interactive command to delete till the
+// start of the current or previous word.
+
+INTERACTIVE void EditBox::delete_backward_word()
+{
+ Point end = cursor;
+ move_backward_word();
+ int len = calc_distance(cursor, end);
+ delete_text(len);
+}
+
+// replace_text() - is a combination of delete_text() and insert_text(). we
+// should use it instead of calling these two methods separately because it
+// records these two operations on the undo stack as one atomic operation.
+
+void EditBox::replace_text(const unichar *str, int len, int delete_len,
+ bool skip_undo)
+{
+ unistring deleted;
+ Point point = cursor;
+ delete_text(delete_len, true, &deleted);
+ insert_text(str, len, true);
+
+ if (!skip_undo) {
+ UndoOp op;
+ op.type = opReplace;
+ op.point = point;
+ op.deleted_text = deleted;
+ op.inserted_text.append(str, len);
+ undo_stack.record_op(op);
+ }
+}
+
+// delete_text() - deletes len characters, starting at the cursor.
+//
+// @param skip_undo - don't record this operation on the undo stack.
+// @param alt_deleted - copy the deleted text here.
+
+void EditBox::delete_text(int len, bool skip_undo, unistring *alt_deleted)
+{
+ if (read_only) {
+ NOTIFY_ERROR(read_only);
+ return;
+ }
+
+ unistring _deleted, *deleted;
+ deleted = alt_deleted ? alt_deleted : &_deleted;
+
+ int parags_deleted = 0; // we keep count of how many parags we delete.
+
+ while (len > 0) {
+ if (cursor.pos == curr_para()->str.len()) {
+ if (cursor.para < parags_count() - 1) {
+ // Delete the EOP. that is, append the next
+ // paragraph to the current paragraph.
+ deleted->push_back(get_curr_eop_char());
+ Paragraph *next_para = paragraphs[cursor.para + 1];
+ curr_para()->str.append(next_para->str);
+ curr_para()->eop = next_para->eop;
+ delete next_para;
+ paragraphs.erase(paragraphs.begin() + cursor.para + 1);
+ }
+ len--;
+ parags_deleted++;
+ } else {
+ // delete the [cursor, end-of-paragraph) segment
+ int to_delete = MIN(curr_para()->str.len() - cursor.pos, len);
+ unichar *cursor_ptr = curr_para()->str.begin() + cursor.pos;
+ deleted->append(cursor_ptr, to_delete);
+ curr_para()->str.erase(cursor_ptr, cursor_ptr + to_delete);
+ len -= to_delete;
+ }
+ }
+
+ // optimization: we save some values and latter check whether they changed.
+ int orig_num_lines = curr_para()->breaks_count();
+ direction_t orig_individual_base_dir = curr_para()->individual_base_dir;
+
+ post_para_modification(*curr_para());
+
+ // if no para was deleted, and the direction of the current para has not
+ // changed, it cannot affect the surrounding parags, so we don't call
+ // calc_contextual_dirs()
+ if (parags_deleted ||
+ curr_para()->individual_base_dir != orig_individual_base_dir)
+ calc_contextual_dirs(cursor.para, cursor.para, true);
+
+ // if no para was deleted, if no screen lines were deleted, we can
+ // update only rgnCurrent instead of the whole window.
+ if (!parags_deleted && curr_para()->breaks_count() == orig_num_lines)
+ request_update(rgnCurrent);
+ else
+ request_update(rgnAll);
+
+ scroll_to_cursor_line();
+
+ if (!skip_undo) {
+ UndoOp op;
+ op.type = opDelete;
+ op.point = cursor;
+ op.deleted_text = *deleted;
+ undo_stack.record_op(op);
+ }
+
+ post_modification();
+}
+
+// insert_text() - inserts text into the buffer. advances the cursor.
+//
+// @param skip_undo - don't record this operation on the undo stack.
+
+void EditBox::insert_text(const unichar *str, int len, bool skip_undo)
+{
+ if (read_only && !is_in_data_transfer()) {
+ if (len)
+ NOTIFY_ERROR_ARG(read_only, str[0]);
+ return;
+ }
+
+ if (!skip_undo) {
+ UndoOp op;
+ op.type = opInsert;
+ op.point = cursor;
+ op.inserted_text.append(str, len);
+ undo_stack.record_op(op);
+ }
+
+ int min_changed_para, max_changed_para; // which parags have changed?
+ min_changed_para = cursor.para;
+
+ while (len > 0) {
+ if (is_eop(str[0])) {
+ // inserting EOP is like pressing Enter: split the current
+ // paragraph into two.
+ Paragraph *p = new Paragraph();
+ p->str.insert(0, &curr_para()->str[cursor.pos],
+ curr_para()->str.end());
+ p->eop = curr_para()->eop;
+ curr_para()->str.erase(&curr_para()->str[cursor.pos],
+ curr_para()->str.end());
+ curr_para()->eop = get_eop_type(str[0]);
+ paragraphs.insert(paragraphs.begin() + cursor.para + 1, p);
+ cursor.para++;
+ cursor.pos = 0;
+ len--;
+ str++;
+ } else {
+ int line_len = 0;
+ while (line_len < len && !is_eop(str[line_len]))
+ line_len++;
+ curr_para()->str.insert(curr_para()->str.begin() + cursor.pos,
+ str, str + line_len);
+ cursor.pos += line_len;
+ len -= line_len;
+ str += line_len;
+ }
+ }
+ max_changed_para = cursor.para;
+
+ // optimization: we save some values and latter check whether they changed.
+ int orig_num_lines = curr_para()->breaks_count();
+ direction_t orig_individual_base_dir = curr_para()->individual_base_dir;
+
+ // update state variables of the affected parags.
+ for (int i = min_changed_para; i <= max_changed_para; i++)
+ post_para_modification(*paragraphs[i]);
+
+ // if only one para was modified, and its direction has not changed,
+ // it doesn't affect the surrounding parags, so we don't call
+ // calc_contextual_dirs()
+ if (!(min_changed_para == max_changed_para &&
+ curr_para()->individual_base_dir == orig_individual_base_dir))
+ calc_contextual_dirs(min_changed_para, max_changed_para, true);
+
+ // if only one para was modified, and no screen lines were added, we can
+ // update only rgnCurrent instead of the whole window.
+ if (min_changed_para == max_changed_para
+ && curr_para()->breaks_count() == orig_num_lines)
+ request_update(rgnCurrent);
+ else
+ request_update(rgnAll);
+
+ scroll_to_cursor_line();
+
+ post_modification();
+}
+
+// }}}
+
diff --git a/editbox.h b/editbox.h
new file mode 100644
index 0000000..062c82f
--- /dev/null
+++ b/editbox.h
@@ -0,0 +1,826 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_EDITBOX_H
+#define BDE_EDITBOX_H
+
+#include <vector>
+#include "directvect.h"
+
+#include "widget.h"
+#include "bidi.h"
+#include "transtbl.h"
+#include "undo.h"
+#include "point.h"
+
+
+// End-of-paragraph type
+enum eop_t { eopNone, eopUnix, eopDOS, eopMac, eopUnicode };
+
+// A "Paragraph" is a fundamental text element in our editor widget,
+// EditBox. EditBox stores the text in paragraphs. Paragraphs correspond
+// to "lines" in traditional unix text editors, but we reserve the term
+// "lines" to screen lines, that is, a single row on the screen.
+
+class Paragraph {
+
+public:
+
+ typedef DirectVector<idx_t> IdxArray;
+
+ unistring str; // the text itself
+
+ // The text is wrapped into several lines to fit the screen width.
+ // Since this wrapping process is costly (calculating widths, etc), we
+ // store the resulting line breaks in "line_breaks" and re-apply the
+ // wrapping process only upon text modification.
+
+ IdxArray line_breaks;
+
+ // The end-of-paragraph character (CR, LF, etc) is not stored directly
+ // in "str", but we keep a record of it in "eop".
+
+ eop_t eop;
+
+ // "individual_base_dir" holds the directionality of the paragraph. It
+ // is updated when the text is modified.
+
+ direction_t individual_base_dir;
+
+ // "contextual_base_dir" holds the directionality of the paragraph
+ // taking into account its surrounding paragraphs (that is, its
+ // context). "individual_base_dir", too, is used in calculating it.
+ // EditBox uses this variable, mainly through is_rtl(), to reorder and
+ // render the text.
+
+ direction_t contextual_base_dir;
+
+public:
+
+ int breaks_count() const { return line_breaks.size(); }
+ direction_t base_dir() const { return contextual_base_dir; }
+ bool is_rtl() const { return contextual_base_dir == dirRTL; }
+
+ Paragraph() {
+ line_breaks.push_back(0);
+ individual_base_dir = contextual_base_dir = dirN;
+ eop = eopNone;
+ }
+
+ void determine_base_dir(diralgo_t dir_algo)
+ {
+ individual_base_dir = BiDi::determine_base_dir(str.begin(),
+ str.len(), dir_algo);
+ // If we're not asked to use the contextual algorithm, then
+ // calculating "contextual_base_dir", which is what EditBox looks
+ // at, is a simple assignment.
+ if (dir_algo != algoContextStrong && dir_algo != algoContextRTL)
+ contextual_base_dir = individual_base_dir;
+ }
+};
+
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#define MAX(x,y) ((x)>(y)?(x):(y))
+
+// EditBox is one widget that doesn't visually notify the user about
+// changes in its state. Such chores are the responsibility of a
+// higher-level class, like the status line or the editor. In order to let
+// other objects know about state changes and various errors, EditBox let's
+// them register themselves as "listeners". This concept is similar to
+// Java's listeners and adapters.
+
+class EditBoxStatusListener {
+public:
+ virtual void on_wrap_change() {}
+ virtual void on_modification_change() {}
+ virtual void on_selection_change() {}
+ virtual void on_position_change() {}
+ virtual void on_auto_indent_change() {}
+ virtual void on_auto_justify_change() {}
+ virtual void on_dir_algo_change() {}
+ virtual void on_alt_kbd_change() {}
+ virtual void on_formatting_marks_change() {}
+ virtual void on_read_only_change() {}
+ virtual void on_smart_typing_change() {}
+ virtual void on_rtl_nsm_change() {}
+ virtual void on_maqaf_change() {}
+ virtual void on_translation_mode_change() {}
+};
+
+class EditBoxErrorListener {
+public:
+ virtual void on_read_only_error(unichar ch = 0) {}
+ virtual void on_no_selection_error() {}
+ virtual void on_no_alt_kbd_error() {}
+ virtual void on_no_translation_table_error() {}
+ virtual void on_cant_display_nsm_error() {}
+};
+
+// A CombinedLine object addresses a line. Remember that a "line" in our
+// terminology is a screen line. Therefore CombineLine is a combination of
+// a paragraph number (para) and a line number inside this paragraph
+// (inner_line).
+
+class CombinedLine {
+
+public:
+
+ int para;
+ int inner_line;
+
+public:
+
+ CombinedLine() {
+ para = 0;
+ inner_line = 0;
+ }
+ CombinedLine(int para, int inner_line) {
+ this->para = para;
+ this->inner_line = inner_line;
+ }
+ CombinedLine(const CombinedLine &other) {
+ para = other.para;
+ inner_line = other.inner_line;
+ }
+ bool operator< (const CombinedLine &other) const {
+ return para < other.para
+ || (para == other.para && inner_line < other.inner_line);
+ }
+ bool operator== (const CombinedLine &other) const {
+ return para == other.para
+ && inner_line == other.inner_line;
+ }
+ bool operator> (const CombinedLine &other) const {
+ return !(*this < other
+ || *this == other);
+ }
+ void swap(CombinedLine &other) {
+ CombinedLine tmp = other;
+ other = *this;
+ *this = tmp;
+ }
+
+ // Some operations, like calculating the difference between two lines,
+ // were meant be used on lines that are close together. the is_near()
+ // method helps us avoid this problem by telling us that the lines are
+ // far apart. (the exact number is unimportant, but it should be of
+ // the same magnitude as the window height.)
+
+ bool is_near(const CombinedLine &other) const {
+ int diff = para - other.para;
+ return diff < 40 && diff > -40;
+ }
+};
+
+// EditBox is the central widget in our editor. It stores the text and does
+// the actual editing.
+//
+// The text is stored as a vector of pointers to Paragraphs. Professional
+// editors are not built that way. What I did here is not an effective
+// data-structure for an editor.
+//
+// It's too late to "fix" this as it would require major modifications.
+// This editor started as a tiny project and from the start I intended it
+// for simple editing tasks, like composing mail messages and nothing more.
+//
+// Data Transfer:
+//
+// EditBox does not handle loading and saving files. This is the task of an
+// I/O module. To allow other objects to access EditBox's textual data, we
+// provide a data transfer interface:
+//
+// start_data_transfer(direction);
+// transfer_data(buffer);
+// end_data_transfer();
+
+class Scrollbar;
+
+class EditBox : public Widget {
+
+public:
+
+ // the following typedef's are shorthands for STL's types. There're
+ // all used by the reordering algorithm (BiDi related).
+
+ typedef DirectVector<level_t> LevelsArray;
+ typedef Paragraph::IdxArray IdxArray;
+ typedef DirectVector<attribute_t> AttributeArray;
+ typedef DirectVector<int> IntArray;
+
+ // EditBox supports two types of wrap (in addition to no-wrap):
+ // wrpAnywhere, that breaks lines even inside words; and
+ // wrpAtWhiteSpace, that doesn't break words (unless they're longer
+ // than the window width).
+
+ enum WrapType { wrpOff, wrpAnywhere, wrpAtWhiteSpace };
+
+ // How to display the Hebrew maqaf?
+ // 1. mqfAsis ("as is") is used when the user has Unicode fonts -- they
+ // usually have maqaf.
+ // 2. 8-bit fonts don't have it, that's why we may have to convert it
+ // to ASCII's dash (mqfTransliterate).
+ // 3. mqfHighlighed makes the maqaf very visible, in case the user
+ // wants to know whether the document is dominated by maqafs.
+
+ enum maqaf_display_t { mqfAsis, mqfTransliterated, mqfHighlighted };
+
+ // How to display RTL NSMs?
+ // 1. rtlnsmOff -- don't print them.
+ // 2. rtlnsmTransliterated -- use ASCII characters to represent them.
+ // 3. rtlnsmAsis -- if the user is lucky to have a system (terminal +
+ // font) that is capable of displaying Hebrew points, print them as-is.
+
+ enum rtl_nsm_display_t { rtlnsmOff, rtlnsmTransliterated, rtlnsmAsis };
+
+ enum syn_hlt_t { synhltOff, synhltHTML, synhltEmail };
+
+ // In the update() method we draw only the region that needs to be
+ // redrawn.
+ // rgnCursor -- only position the cursor; rgnCurrent -- draw the
+ // current paragraph and position the cursor; rgnRange -- draw a range
+ // of paragraphs; argnAll -- draw all visible paragraphs.
+
+ enum region { rgnNone = 0, rgnCursor = 1, rgnCurrent = 2,
+ rgnRange = 4, rgnAll = 8 };
+
+ enum data_transfer_direction { dataTransferIn, dataTransferOut };
+
+protected:
+
+ // for translating the next char typed.
+ static TranslationTable transtbl;
+
+ // ASCII representations of various non-displayable characters and
+ // concepts, like line continuation.
+ static TranslationTable reprtbl;
+
+ // Alternate keyboard -- usually the user's national language.
+ static TranslationTable altkbdtbl;
+
+ EditBoxStatusListener *status_listener;
+ EditBoxErrorListener *error_listener;
+
+#define NOTIFY_CHANGE(event) \
+ do { \
+ if (status_listener) \
+ status_listener->on_ ## event ## _change(); \
+ } while (0)
+
+#define NOTIFY_ERROR(event) \
+ do { \
+ if (error_listener) \
+ error_listener->on_ ## event ## _error(); \
+ } while (0)
+
+#define NOTIFY_ERROR_ARG(event, arg) \
+ do { \
+ if (error_listener) \
+ error_listener->on_ ## event ## _error(arg); \
+ } while (0)
+
+ // The text itself: a vector of pointers to Paragraphs
+ std::vector<Paragraph *> paragraphs;
+
+ // The cursor position
+ Point cursor;
+
+ UndoStack undo_stack;
+ unistring clipboard;
+
+ // Margins make it easier for us to implement features such as line
+ // numbering. Currently EditBox only uses "margin_after" to reserve
+ // one column for the '$' character (the indicator that, in no-wrap
+ // mode, tells the user that only part of the line is shown), and for
+ // the cursor, when it stands past the last character in the line.
+ //
+ // Other editors call these "left_margin" and "right_margin", but since
+ // this is a BiDi editor, where paragraphs may be RTL oriented, I use
+ // "before" and "after" to mean "left" and "right", respectively, for
+ // LTR paragraphs, and "right" and "left", respectively, for RTL
+ // paragraphs.
+
+ int margin_before;
+ int margin_after;
+
+ // top_line refers to the first visible line in our window.
+ CombinedLine top_line;
+
+ // The "selection" (shown highlighted when active) stretches from the
+ // curser position till this primary_mark.
+ Point primary_mark;
+
+ // When the user moves the cursor vertically, the editor tries to
+ // reposition the cursor on the same visual column. This is not always
+ // possible, e.g. when that column would be inside a TAB segment, or
+ // when the new line is not long enough to contain this column.
+ // optinal_vis_colum holds the column on which the cursor _should_
+ // stand.
+ int optimal_vis_column;
+
+ // Has the buffer been modified?
+ bool modified;
+
+ // justification_column holds the maximum line length to use when
+ // justifying paragraphs.
+ idx_t justification_column;
+
+ // when rfc2646_trailing_space is true, we keep a trailing space on
+ // each line when justifying paragraphs.
+ bool rfc2646_trailing_space;
+
+ // When the user moves the cursor past the top or the bottom of the window,
+ // EditBox scrolls scroll_step lines up or down.
+ int scroll_step;
+
+ // update_region tells the update() method which part of the window to
+ // paint. it's a bitmask of 'enum region'.
+ int update_region;
+ // when drawing a range of paragraphs (rgnRange), update_from and
+ // update_to denote the region.
+ int update_from;
+ int update_to;
+
+ // Modes
+ bool alt_kbd; // is the alternative keyboard active? (cf. altkbdtbl)
+ bool auto_justify; // is auto-justify mode active?
+ bool auto_indent; // whether to auto-indent lines.
+ bool translation_mode; // translate next char? (cf. transtbl)
+ bool read_only; // is the user allowed to make changes to the buffer?
+ bool smart_typing; // replace simple ASCII chars with fancy ones.
+
+ // Display
+ WrapType wrap_type;
+ diralgo_t dir_algo;
+ int tab_width;
+ bool show_explicits; // display BiDi formatting codes?
+ bool show_paragraph_endings; // display paragraph endings?
+ bool show_tabs;
+ rtl_nsm_display_t rtl_nsm_display;
+ maqaf_display_t maqaf_display;
+
+ syn_hlt_t syn_hlt;
+ bool underline_hlt;
+
+ int non_interactive_text_width;
+ // on resize event, compare new width to old_width
+ // and wrap lines only if they're different.
+ int old_width;
+
+ bool bidi_enabled;
+
+ bool visual_cursor_movement;
+
+ // Where the last edit operation took place?
+ Point last_modification;
+
+ // We emulate some emacs commands (e.g. C-k) with these
+ // flags. When prev_command_type is cmdtpKill, we know
+ // we need to append to the clipboard.
+ enum CommandType { cmdtpUnknown, cmdtpKill };
+ CommandType prev_command_type, current_command_type;
+
+private:
+
+ struct _t_data_transfer {
+ bool in_transfer;
+ data_transfer_direction dir;
+ Point cursor_origin;
+ bool skip_undo;
+ bool clear_modified_flag;
+ bool at_eof;
+ bool prev_is_cr;
+ int ntransferred_out;
+ int ntransferred_out_max;
+ } data_transfer;
+
+public:
+
+ void set_read_only(bool value);
+ bool is_read_only() const { return read_only; }
+ INTERACTIVE void toggle_read_only() { set_read_only(!is_read_only()); }
+
+ void set_cursor_position(const Point &point);
+ void get_cursor_position(Point& point) const { point = cursor; }
+
+ void set_maqaf_display(maqaf_display_t disp);
+ maqaf_display_t get_maqaf_display() const { return maqaf_display; }
+ INTERACTIVE void toggle_maqaf();
+ INTERACTIVE void set_maqaf_display_transliterated() {
+ set_maqaf_display(mqfTransliterated);
+ }
+ INTERACTIVE void set_maqaf_display_highlighted() {
+ set_maqaf_display(mqfHighlighted);
+ }
+ INTERACTIVE void set_maqaf_display_asis() {
+ set_maqaf_display(mqfAsis);
+ }
+
+ void set_smart_typing(bool val);
+ bool is_smart_typing() const { return smart_typing; }
+ INTERACTIVE void toggle_smart_typing()
+ { set_smart_typing(!is_smart_typing()); }
+
+ bool set_rtl_nsm_display(rtl_nsm_display_t disp);
+ rtl_nsm_display_t get_rtl_nsm_display() const
+ { return rtl_nsm_display; }
+ INTERACTIVE void toggle_rtl_nsm();
+ INTERACTIVE void set_rtl_nsm_off() {
+ set_rtl_nsm_display(rtlnsmOff);
+ }
+ INTERACTIVE void set_rtl_nsm_asis() {
+ set_rtl_nsm_display(rtlnsmAsis);
+ }
+ INTERACTIVE void set_rtl_nsm_transliterated() {
+ set_rtl_nsm_display(rtlnsmTransliterated);
+ }
+
+ void set_formatting_marks(bool value);
+ bool has_formatting_marks() const { return show_paragraph_endings; }
+ INTERACTIVE void toggle_formatting_marks();
+
+ void set_tab_width(int value);
+ int get_tab_width() const { return tab_width; }
+
+ void set_modified(bool value);
+ bool is_modified() const { return modified; }
+
+ void set_auto_indent(bool value);
+ bool is_auto_indent() const { return auto_indent; }
+ INTERACTIVE void toggle_auto_indent()
+ { set_auto_indent(!is_auto_indent()); }
+
+ void set_auto_justify(bool value);
+ bool is_auto_justify() const { return auto_justify; }
+ INTERACTIVE void toggle_auto_justify()
+ { set_auto_justify(!is_auto_justify()); }
+
+ void set_justification_column(int value);
+ int get_justification_column() const { return justification_column; }
+ void set_rfc2646_trailing_space(bool value) { rfc2646_trailing_space = value; }
+
+ void set_translation_table(const TranslationTable &tbl) { transtbl = tbl; }
+ void set_translation_mode(bool value);
+ bool in_translation_mode() const { return translation_mode; }
+ INTERACTIVE void set_translate_next_char() { set_translation_mode(true); }
+
+ void set_scroll_step(int value);
+ int get_scroll_step() const { return scroll_step; }
+
+ void set_wrap_type(WrapType value);
+ WrapType get_wrap_type() const { return wrap_type; }
+ INTERACTIVE void toggle_wrap();
+ INTERACTIVE void set_wrap_type_at_white_space() {
+ set_wrap_type(wrpAtWhiteSpace);
+ }
+ INTERACTIVE void set_wrap_type_anywhere() {
+ set_wrap_type(wrpAnywhere);
+ }
+ INTERACTIVE void set_wrap_type_off() {
+ set_wrap_type(wrpOff);
+ }
+ void set_non_interactive_text_width(int cols)
+ { non_interactive_text_width = cols; }
+
+ void set_dir_algo(diralgo_t value);
+ diralgo_t get_dir_algo() const { return dir_algo; }
+ INTERACTIVE void toggle_dir_algo();
+ INTERACTIVE void set_dir_algo_unicode() { set_dir_algo(algoUnicode); }
+ INTERACTIVE void set_dir_algo_force_ltr() { set_dir_algo(algoForceLTR); }
+ INTERACTIVE void set_dir_algo_force_rtl() { set_dir_algo(algoForceRTL); }
+ INTERACTIVE void set_dir_algo_context_strong() { set_dir_algo(algoContextStrong); }
+ INTERACTIVE void set_dir_algo_context_rtl() { set_dir_algo(algoContextRTL); }
+
+ void set_alt_kbd_table(const TranslationTable &tbl) { altkbdtbl = tbl; }
+ void set_alt_kbd(bool val);
+ bool get_alt_kbd() const { return alt_kbd; }
+ INTERACTIVE void toggle_alt_kbd();
+
+ void set_primary_mark(const Point &point);
+ void set_primary_mark() { set_primary_mark(cursor); }
+ void unset_primary_mark();
+ bool is_primary_mark_set() const { return primary_mark.para != -1; }
+ bool has_selected_text() const { return is_primary_mark_set(); }
+ INTERACTIVE void toggle_primary_mark();
+
+ void set_repr_table(const TranslationTable &tbl)
+ { reprtbl = tbl; }
+ void set_status_listener(EditBoxStatusListener *obj)
+ { status_listener = obj; }
+ void set_error_listener(EditBoxErrorListener *obj)
+ { error_listener = obj; }
+ void set_undo_size_limit(size_t limit)
+ { undo_stack.set_size_limit(limit); }
+ void set_key_for_key_undo(bool value)
+ { undo_stack.set_merge(!value); }
+ bool is_key_for_key_undo() const
+ { return !undo_stack.is_merge(); }
+ INTERACTIVE void toggle_key_for_key_undo();
+ void sync_scrollbar(Scrollbar *scrollbar);
+
+ void enable_bidi(bool value);
+ bool is_bidi_enabled() const { return bidi_enabled; }
+ INTERACTIVE void toggle_bidi();
+
+ void set_syn_hlt(syn_hlt_t sh);
+ syn_hlt_t get_syn_hlt() const
+ { return syn_hlt; }
+ void detect_syntax();
+
+ INTERACTIVE void menu_set_syn_hlt_none();
+ INTERACTIVE void menu_set_syn_hlt_html();
+ INTERACTIVE void menu_set_syn_hlt_email();
+
+ void set_underline(bool v);
+ bool get_underline() const
+ { return underline_hlt; }
+ INTERACTIVE void toggle_underline();
+
+ void set_visual_cursor_movement(bool v);
+ bool get_visual_cursor_movement() const { return visual_cursor_movement; }
+ INTERACTIVE void toggle_visual_cursor_movement();
+
+protected:
+
+ int parags_count() const { return paragraphs.size(); }
+
+ inline Paragraph *curr_para() { return paragraphs[cursor.para]; };
+
+public:
+
+ HAS_ACTIONS_MAP(EditBox, Widget);
+ HAS_BINDINGS_MAP(EditBox, Widget);
+
+ ///////////////////////////////////////////////////////////////////////
+ // Initialization
+ ///////////////////////////////////////////////////////////////////////
+
+ EditBox();
+ virtual ~EditBox();
+ void new_document();
+
+ ///////////////////////////////////////////////////////////////////////
+ // Data Transfer
+ ///////////////////////////////////////////////////////////////////////
+
+public:
+
+ void start_data_transfer(data_transfer_direction dir,
+ bool new_document = false,
+ bool selection_only = false);
+ int transfer_data(unichar *buf, int len);
+ void end_data_transfer();
+
+protected:
+
+ int transfer_data_in(unichar *data, int len);
+ int transfer_data_out(unichar *buf, int len);
+ bool is_in_data_transfer() const;
+
+ ///////////////////////////////////////////////////////////////////////
+ // Movement
+ ///////////////////////////////////////////////////////////////////////
+
+public:
+
+ INTERACTIVE void move_forward_char();
+ INTERACTIVE void move_backward_char();
+ INTERACTIVE void move_next_line();
+ INTERACTIVE void move_previous_line();
+ INTERACTIVE void move_forward_page();
+ INTERACTIVE void move_backward_page();
+ INTERACTIVE void move_beginning_of_line();
+ INTERACTIVE void move_end_of_line();
+ INTERACTIVE void move_beginning_of_buffer();
+ INTERACTIVE void move_end_of_buffer();
+ INTERACTIVE void move_backward_word();
+ INTERACTIVE void move_forward_word();
+ INTERACTIVE void move_last_modification();
+ INTERACTIVE void key_left();
+ INTERACTIVE void key_right();
+ INTERACTIVE void key_home();
+ INTERACTIVE void center_line();
+ void move_first_char(unichar ch);
+ bool search_forward(unistring str);
+ unichar get_current_char();
+ void move_absolute_line(int line);
+
+ // Visual cursor movement:
+ INTERACTIVE void move_forward_visual_char();
+ INTERACTIVE void move_backward_visual_char();
+ INTERACTIVE void move_beginning_of_visual_line();
+
+protected:
+
+ void post_horizontal_movement();
+ void post_vertical_movement();
+ bool is_at_end_of_buffer();
+ bool is_at_beginning_of_buffer();
+ bool is_at_beginning_of_word();
+ void move_relative_line(int diff);
+ void add_rows_to_line(CombinedLine &line, int rows);
+ int lines_diff(CombinedLine L1, CombinedLine L2);
+ void scroll_to_cursor_line();
+ int calc_inner_line();
+ int calc_vis_column();
+ void move_to_vis_column(int column);
+ void invalidate_optimal_vis_column()
+ { optimal_vis_column = -1; }
+ bool valid_optimal_vis_column() const
+ { return optimal_vis_column != -1; }
+ int get_effective_scroll_step() const
+ { return MAX(1, MIN(window_height() / 2 + 0 , scroll_step)); }
+
+ // Visual cursor movement related:
+ bool is_at_end_of_screen_line();
+ bool is_at_first_screen_line();
+ bool is_at_last_screen_line();
+
+ ///////////////////////////////////////////////////////////////////////
+ // Modification
+ ///////////////////////////////////////////////////////////////////////
+
+public:
+
+ INTERACTIVE void delete_paragraph();
+ INTERACTIVE void cut_end_of_paragraph();
+ INTERACTIVE void delete_forward_word();
+ INTERACTIVE void delete_backward_word();
+ INTERACTIVE void delete_forward_char();
+ INTERACTIVE void delete_backward_char();
+ void delete_text(int len, bool skip_undo = false,
+ unistring *alt_deleted = NULL);
+ void insert_char(unichar ch);
+ void insert_text(const unichar *str, int len, bool skip_undo = false);
+ void insert_text(const unistring &str, bool skip_undo = false)
+ { insert_text(str.begin(), str.len(), skip_undo); }
+ void replace_text(const unichar *str, int len, int delete_len,
+ bool skip_undo = false);
+ void replace_text(const unistring &str, int delete_len,
+ bool skip_undo = false)
+ { replace_text(str.begin(), str.len(), delete_len, skip_undo); }
+ INTERACTIVE void copy();
+ INTERACTIVE void cut();
+ INTERACTIVE void paste();
+ INTERACTIVE void undo();
+ INTERACTIVE void redo();
+ INTERACTIVE void justify();
+ INTERACTIVE void insert_maqaf();
+ INTERACTIVE void toggle_eops();
+ void set_eops(eop_t new_eop);
+ eop_t get_dominant_eop() { return paragraphs[0]->eop; }
+ INTERACTIVE void set_eops_unix() { set_eops(eopUnix); }
+ INTERACTIVE void set_eops_dos() { set_eops(eopDOS); }
+ INTERACTIVE void set_eops_mac() { set_eops(eopMac); }
+ INTERACTIVE void set_eops_unicode() { set_eops(eopUnicode); }
+ void log2vis(const char *options);
+ void key_enter();
+ void key_dash();
+
+protected:
+
+ void post_modification();
+ void post_para_modification(Paragraph &p);
+ void undo_op(UndoOp *opp);
+ void redo_op(UndoOp *opp);
+ void calc_contextual_dirs(int min_para, int max_para, bool update_display);
+ inline void set_contextual_dir(Paragraph &p, direction_t dir,
+ bool update_display);
+ int calc_distance(Point p1, Point p2);
+ void copy_text(int len, bool append = false);
+ void cut_or_copy(bool just_copy);
+ inline unichar get_eop_char(eop_t eop);
+ inline unichar get_curr_eop_char();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Rendering
+ ////////////////////////////////////////////////////////////////////////////
+public:
+
+ virtual void update();
+ virtual void update_cursor() { request_update(rgnCursor); update(); }
+ virtual bool is_dirty() const { return update_region != rgnNone; }
+ virtual void invalidate_view() { request_update(rgnAll); }
+ void reformat();
+
+protected:
+
+ // get_char_width() returns the width of a characetr, but since
+ // this character may be part of a LAM-ALEF ligature, we need
+ // to keep state information between calls - wdstate.
+ struct wdstate {
+ bool may_start_lam_alef;
+ void clear() {
+ may_start_lam_alef = false;
+ }
+ wdstate() {
+ clear();
+ }
+ };
+
+ int get_text_width() const;
+ // :TODO: try to make this inline. FreeBSD's compiler complains...
+ /*inline*/
+ int get_char_width(unichar ch, int pos,
+ wdstate *stt = NULL, bool visual = false);
+ int get_str_width(const unichar *str, idx_t len, bool visual = false);
+ int get_rev_str_width(const unichar *str, idx_t len);
+ void wrap_para(Paragraph &para);
+ void rewrap_all();
+ void request_update(region rgn);
+ void request_update(int lo, int hi);
+
+ struct _t_cache {
+ int owner_para;
+ LevelsArray levels;
+ IdxArray position_L_to_V;
+ IdxArray position_V_to_L;
+ AttributeArray attributes;
+ unistring vis;
+
+ void invalidate() {
+ owner_para = -1;
+ }
+ bool owned_by(int para) {
+ return owner_para == para;
+ }
+ void set_owner(int para) {
+ owner_para = para;
+ }
+ } cache;
+
+ virtual void redraw_paragraph(
+ Paragraph &p,
+ int window_start_line,
+ bool only_cursor,
+ int para_num
+ );
+
+ void EditBox::redraw_unwrapped_paragraph(
+ Paragraph &p,
+ int window_start_line,
+ bool only_cursor,
+ int para_num,
+ LevelsArray &levels,
+ IdxArray& position_L_to_V,
+ IdxArray& position_V_to_L,
+ AttributeArray& attributes,
+ bool eop_is_selected
+ );
+
+ void EditBox::redraw_wrapped_paragraph(
+ Paragraph &p,
+ int window_start_line,
+ bool only_cursor,
+ int para_num,
+ LevelsArray &levels,
+ IdxArray& position_L_to_V,
+ IdxArray& position_V_to_L,
+ AttributeArray& attributes,
+ bool eop_is_selected
+ );
+
+ void draw_unistr(const unichar *str, idx_t len,
+ attribute_t *attributes = NULL, int *tab_widths = NULL);
+ void calc_tab_widths(const unichar *str, idx_t len,
+ bool rev, IntArray &tab_widths);
+ void draw_rev_unistr(const unichar *str, idx_t len,
+ attribute_t *attributes = NULL);
+ unichar get_char_repr(unichar ch);
+ void put_unichar_attr_at(int line, int col, unichar ch, int attr);
+ void draw_eop(int y, int x, Paragraph &p, bool selected);
+ virtual void do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num);
+
+ ///////////////////////////////////////////////////////////////////////
+ // Misc
+ ///////////////////////////////////////////////////////////////////////
+
+public:
+
+ virtual bool handle_event(const Event &evt);
+ virtual void resize(int lines, int columns, int y, int x);
+
+public:
+
+ int get_number_of_paragraphs() const
+ { return parags_count(); }
+ const unistring &get_paragraph_text(int i) {
+ return paragraphs[i]->str;
+ }
+};
+
+#endif
+
diff --git a/editbox2.cc b/editbox2.cc
new file mode 100644
index 0000000..2d055ea
--- /dev/null
+++ b/editbox2.cc
@@ -0,0 +1,1759 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "editbox.h"
+#include "transtbl.h"
+#include "univalues.h"
+#include "mk_wcwidth.h"
+#include "my_wctob.h"
+#include "shaping.h"
+#include "themes.h"
+#include "dbg.h"
+
+// Default representations for some characters and concepts.
+// If you want to change these, don't edit this file; edit "reprtab" instead.
+
+#define FAILED_CONV_REPR '?'
+#define CONTROL_REPR '^'
+#define NSM_REPR '\''
+#define WIDE_REPR 'A'
+#define TRIM_REPR '$'
+#define WRAP_RTL_REPR '/'
+#define WRAP_LTR_REPR '\\'
+
+#define EOP_UNICODE_REPR 0xB6
+#define EOP_DOS_REPR 0xB5
+#define EOP_UNIX_LTR_REPR 0xAB
+#define EOP_UNIX_RTL_REPR 0xBB
+#define EOP_MAC_REPR '@'
+#define EOP_NONE_REPR 0xAC
+
+static bool do_mirror = true; // Do mirroring also when !bidi_enabled ?
+
+// Setters and Togglers for various display options {{{
+
+void EditBox::set_formatting_marks(bool value)
+{
+ show_paragraph_endings = value;
+ show_explicits = value;
+ show_tabs = value;
+ rewrap_all(); // width changes because we show/hide explicit marks
+ NOTIFY_CHANGE(formatting_marks);
+}
+
+// toggle_formatting_marks() - interactive command to toggle display of
+// formatting marks.
+
+INTERACTIVE void EditBox::toggle_formatting_marks()
+{
+ set_formatting_marks(!has_formatting_marks());
+}
+
+void EditBox::set_maqaf_display(maqaf_display_t disp)
+{
+ maqaf_display = disp;
+ request_update(rgnAll);
+ NOTIFY_CHANGE(maqaf);
+}
+
+// toggle_maqaf() - interactive command to toggle the display of maqaf.
+
+INTERACTIVE void EditBox::toggle_maqaf()
+{
+ switch (maqaf_display) {
+ case mqfAsis: set_maqaf_display(mqfTransliterated); break;
+ case mqfTransliterated: set_maqaf_display(mqfHighlighted); break;
+ case mqfHighlighted: set_maqaf_display(mqfAsis); break;
+ }
+}
+
+bool EditBox::set_rtl_nsm_display(rtl_nsm_display_t disp)
+{
+ if (disp == rtlnsmAsis && (!terminal::is_utf8 || terminal::is_fixed)) {
+ NOTIFY_ERROR(cant_display_nsm);
+ return false;
+ }
+ rtl_nsm_display = disp;
+ rewrap_all();
+ NOTIFY_CHANGE(rtl_nsm);
+ return true;
+}
+
+// toggle_rtl_nsm() - interactive command to toggle display of Hebrew
+// points.
+
+INTERACTIVE void EditBox::toggle_rtl_nsm()
+{
+ switch (rtl_nsm_display) {
+ case rtlnsmOff:
+ set_rtl_nsm_display(rtlnsmTransliterated);
+ break;
+ case rtlnsmTransliterated:
+ if (set_rtl_nsm_display(rtlnsmAsis))
+ break;
+ // fall-through
+ case rtlnsmAsis:
+ set_rtl_nsm_display(rtlnsmOff);
+ break;
+ }
+}
+
+void EditBox::enable_bidi(bool value)
+{
+ bidi_enabled = value;
+ cache.invalidate();
+ invalidate_optimal_vis_column();
+ request_update(rgnAll);
+}
+
+INTERACTIVE void EditBox::toggle_bidi()
+{
+ enable_bidi(!is_bidi_enabled());
+}
+
+// }}}
+
+// Updating {{{
+
+// request_update() - request that some region be repainted next time the
+// update() method is called.
+
+void EditBox::request_update(region rgn)
+{
+ update_region |= rgn;
+}
+
+// request_update(from, to) - similar to request_update(rgnAll), but only
+// paints the paragraphs in the range [from, to].
+
+void EditBox::request_update(int from, int to)
+{
+ if (update_region & rgnRange) {
+ // if there's already a pending rgnRange request,
+ // give up and paint the whole window.
+ update_region = rgnAll;
+ } else {
+ update_region |= rgnRange;
+ update_from = from;
+ update_to = to;
+ }
+}
+
+void EditBox::update()
+{
+ if (update_region == rgnNone)
+ return;
+
+ static int last_cursor_para = -1;
+ if (is_primary_mark_set()) {
+ // Determining which paragraphs to repaint when a selection is active
+ // is a bit complicated, so we revert to a simple decision: if the
+ // cursor hasn't moved from the paragraph we painted in the previous
+ // update then the selection hasn't moved to include new paragraphs
+ // and we can paint only this paragraph; else repaint the whole
+ // window.
+ if (last_cursor_para == cursor.para) {
+ // we repaint the whole paragraph even if we're asked to only
+ // reposition the cursor, because the selection has changed.
+ update_region |= rgnCurrent;
+ } else {
+ update_region = rgnAll;
+ }
+ last_cursor_para = cursor.para;
+ }
+
+ if (wrap_type == wrpOff && update_region == rgnCursor) {
+ // Well... this is a flaw in our update mechanism. In no-wrap mode we
+ // repaint the whole paragraph even if we're asked to only reposition
+ // the cursor. that's because the new cursor position might be in a
+ // segment which is not shown on screen.
+ update_region = rgnCurrent;
+ }
+
+ if (update_region & rgnAll) {
+ wbkgd(wnd, get_attr(EDIT_ATTR));
+ werase(wnd);
+ // we invalidate the cache here instead of doing it after every change
+ // that affects the display.
+ cache.invalidate();
+ }
+
+ int window_line = -top_line.inner_line;
+ int curr_para_line = 0; // the window line at which the current para starts.
+ // "=0" to silence the compiler.
+
+ for (int i = top_line.para;
+ i < parags_count()
+ && window_line < window_height();
+ i++) {
+ Paragraph *para = paragraphs[i];
+ if (para == curr_para()) {
+ // we paint the current para outside of the loop because its
+ // painting also positions the cursor (which subsequent draws
+ // will invalidate).
+ curr_para_line = window_line;
+ if (update_region != rgnCursor) {
+ // we don't paint it yet, but we erase its background
+ for (int k = window_line;
+ k < window_line + para->breaks_count()
+ && k < window_height();
+ k++) {
+ wmove(wnd, k, 0);
+ wclrtoeol(wnd);
+ }
+ }
+ }
+ else if ((update_region & rgnAll)
+ || ((update_region & rgnRange)
+ && i >= update_from && i <= update_to)) {
+ redraw_paragraph(*para, window_line, false, i);
+ }
+ window_line += para->breaks_count();
+ }
+
+ // paint the current paragraph
+ bool only_cursor = (update_region == rgnCursor);
+ redraw_paragraph(*curr_para(), curr_para_line, only_cursor, cursor.para);
+
+ wnoutrefresh(wnd);
+ update_region = rgnNone;
+}
+
+// }}}
+
+// BiDi Reordering {{{
+
+template <class VAL, class IDX>
+inline void reverse(VAL *array, IDX len)
+{
+ if (len == 0)
+ return; // IDX may be unsigned, so "-1" won't work.
+ for (IDX start = 0, end = len - 1; start < end; start++, end--) {
+ VAL tmp = array[start];
+ array[start] = array[end];
+ array[end] = tmp;
+ }
+}
+
+// reorder() - Reorder Resolved Levels. Rules L2..L4. Unfortunately, FriBiDi
+// has a flawed interface: this should be done after line-wrapping, and
+// FriBiDi doesn't have a separate function for reordering, so we have to do
+// it ourselves. (but it isn't so bad: that way we are not dependent on the
+// BiDi engine, because we use very little of it.)
+
+static void reorder(level_t *levels, idx_t len,
+ idx_t *position_V_to_L, idx_t *position_L_to_V,
+ unichar *str,
+ attribute_t *attributes = NULL,
+ bool mirror = false,
+ bool reorder_nsm = false)
+{
+ // We update a V_to_L vector from a L_to_V one. If the user
+ // doesn't provide a L_to_V vector, we have to allocate it
+ // ourselves.
+ EditBox::IdxArray internal_position_V_to_L;
+
+ if (position_L_to_V && !position_V_to_L) {
+ internal_position_V_to_L.resize(len);
+ position_V_to_L = internal_position_V_to_L.begin();
+ }
+
+ if (position_V_to_L) {
+ for (idx_t i = 0; i < len; i++)
+ position_V_to_L[i] = i;
+ }
+
+ level_t highest_level = 0;
+ level_t lowest_odd_level = 63;
+ for (idx_t i = 0; i < len; i++) {
+ level_t level = levels[i];
+ if (level > highest_level)
+ highest_level = level;
+ if ((level & 1) && level < lowest_odd_level)
+ lowest_odd_level = level;
+ }
+
+ // L2
+ for (level_t level = highest_level; level >= lowest_odd_level; level--)
+ for (idx_t i = 0; i < len; i++)
+ if (levels[i] >= level) {
+ idx_t start = i;
+ while (i < len && levels[i] >= level)
+ i++;
+
+ if (position_V_to_L)
+ reverse(position_V_to_L + start, i - start);
+ reverse(levels + start, i - start);
+ if (str)
+ reverse(str + start, i - start);
+ if (attributes)
+ reverse(attributes + start, i - start);
+ }
+
+ // L3, L4: Mirroring and NSM reordering.
+ if (str && (mirror || reorder_nsm)) {
+ for (idx_t i = 0; i < len; i++) {
+ if (levels[i] & 1) {
+ BiDi::mirror_char(&str[i]);
+ // we don't unconditionally reorder NSMs bacause when the
+ // user prefers ASCII transliteration they should be left
+ // to the left of the character.
+ if (reorder_nsm
+ && BiDi::is_nsm(str[i]))
+ {
+ idx_t start = i++;
+ while (i < len && BiDi::is_nsm(str[i]))
+ i++;
+ if (i < len) // :FIXME: only if on the same level.
+ i++; // include the base character.
+
+ // should I reorder position_V_to_L and attributes too?
+ //if (position_V_to_L)
+ // reverse(position_V_to_L + start, i - start);
+ reverse(str + start, i - start);
+ //if (attributes)
+ // reverse(attributes + start, i - start);
+ if (i < len)
+ i--;
+ }
+ }
+ }
+ }
+
+ if (position_L_to_V) {
+ for (idx_t i = 0; i < len; i++)
+ position_L_to_V[position_V_to_L[i]] = i;
+ }
+}
+
+// }}}
+
+// Wrap / character traits {{{
+
+// most of the characters will probably be "simple" ones, meaning
+// they occupy one terminal column. IS_SIMPLE_ONE_COLUMN_CHAR helps
+// us avoid a call to wcwidth().
+
+#define IS_SIMPLE_ONE_COLUMN_CHAR(ch) \
+ ((ch >= 32 && ch <= 126) \
+ || (ch >= UNI_HEB_ALEF && ch <= UNI_HEB_TAV))
+
+// get_char_width() - returns the width, in terminal columns, of a character.
+// it takes into account the user's preferences (e.g. whether to show explicit
+// marks), LAM-ALEF ligature, and the terminal capabilities (e.g. whether it's
+// capable of displaying wide and combining characters). it also calculates
+// the width of a TAB character based on its position in the line (pos).
+
+int EditBox::get_char_width(unichar ch, int pos, wdstate *stt, bool visual)
+{
+#define LAM_N 0x0644
+#define IS_LAM_N(c) ((c) == LAM_N)
+
+#define ALEF_MADDA_N 0x0622
+#define ALEF_HAMZA_ABOVE_N 0x0623
+#define ALEF_HAMZA_BELOW_N 0x0625
+#define ALEF_N 0x0627
+#define IS_ALEF_N(c) ((c) == ALEF_MADDA_N \
+ || (c) == ALEF_HAMZA_ABOVE_N \
+ || (c) == ALEF_HAMZA_BELOW_N \
+ || (c) == ALEF_N)
+
+ // optimization
+ if (IS_SIMPLE_ONE_COLUMN_CHAR(ch) && !(stt && stt->may_start_lam_alef))
+ return 1;
+
+ int width = mk_wcwidth(ch);
+
+ // handle ALEF-LAM ligature
+ if (terminal::do_arabic_shaping && stt) {
+ if (visual ? IS_ALEF_N(ch) : IS_LAM_N(ch)) {
+ stt->may_start_lam_alef = true;
+ } else {
+ if (stt->may_start_lam_alef
+ && (visual ? IS_LAM_N(ch) : IS_ALEF_N(ch))) {
+ // yes, this is the second character of the ligature.
+ // return 0 (the ligature width is 1, and the first
+ // character already returned 1).
+ stt->may_start_lam_alef = false;
+ return 0;
+ } else {
+ if (width != 0 || !is_shaping_transparent(ch))
+ stt->may_start_lam_alef = false;
+ }
+ }
+ }
+
+ if (width != 1) {
+ if (ch == '\t') {
+ return (tab_width - (pos % tab_width));
+ } else if (width == -1 || ch == '\0') {
+ // We print control characters and nulls on one column, so
+ // we set their width to 1.
+ width = 1;
+ } else if (width == 0) {
+ // Calculate required width for BIDI codes and Hebrew points
+ // based on user's preferences.
+ if (BiDi::is_explicit_mark(ch)) {
+ if (show_explicits)
+ width = 1;
+ } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
+ if (rtl_nsm_display == rtlnsmTransliterated)
+ width = 1;
+ } else {
+ // Now handle general NSMs:
+ // if it's not a UTF-8 terminal, assume it can't combine
+ // characters. Same for fixed terminals.
+ if (!terminal::is_utf8 || terminal::is_fixed)
+ width = 1;
+ }
+ } else {
+ // width == 2, Asian ideographs
+ if (!terminal::is_utf8 || terminal::is_fixed)
+ width = 1;
+ }
+ }
+
+ return width;
+}
+
+int EditBox::get_str_width(const unichar *str, idx_t len, bool visual)
+{
+ wdstate stt;
+ int width = 0;
+ while (len--)
+ width += get_char_width(*str++, width, &stt, true);
+ return width;
+}
+
+// get_rev_str_width() - sums the widths of the characters starting at the
+// end of the string. it makes a difference only for the TAB character.
+
+int EditBox::get_rev_str_width(const unichar *str, idx_t len)
+{
+ wdstate stt;
+ int width = 0;
+ str += len;
+ while (len--)
+ width += get_char_width(*(--str), width, &stt);
+ return width;
+}
+
+// calc_vis_column() - returns the visual column the cursor is at.
+// It does a logical-to-visual conversion, then it sums, in visual order,
+// the widths of the characters till the cursor.
+
+int EditBox::calc_vis_column()
+{
+ Paragraph &p = *curr_para();
+
+ // Find the start and the end of the screen line.
+ int inner_line = calc_inner_line();
+ idx_t start = (inner_line > 0)
+ ? p.line_breaks[inner_line - 1]
+ : 0;
+ idx_t end = p.line_breaks[inner_line];
+ idx_t line_len = end - start;
+
+ LevelsArray &levels = cache.levels;
+ IdxArray &position_L_to_V = cache.position_L_to_V;
+ unistring &vis = cache.vis;
+ // We apply the BiDi algorithm if the
+ // results are not already cached.
+ if (!cache.owned_by(cursor.para)) {
+ cache.invalidate(); // it's owned by someone else
+ levels.resize(p.str.len());
+ DBG(100, ("get_embedding_levels() - by calc_vis_column()\n"));
+ BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
+ p.base_dir(), levels.begin(),
+ p.breaks_count(), p.line_breaks.begin(),
+ !bidi_enabled);
+ vis = p.str;
+ position_L_to_V.resize(p.str.len());
+ // reorder just the screen line.
+ reorder(levels.begin() + start, line_len,
+ NULL, position_L_to_V.begin() + start, vis.begin() + start);
+ }
+
+ int cursor_log_line_pos = cursor.pos - start;
+ // Note that we use the term "width" to make it clear that we
+ // sum the widths of the characters. "column", in the name of
+ // this method, refers to the terminal column.
+ int cursor_vis_width = 0;
+
+ if (cursor_log_line_pos >= line_len) {
+ // cursor stands at end of line. calculate the
+ // width of the whole line.
+ if (p.is_rtl())
+ cursor_vis_width = get_rev_str_width(vis.begin() + start,
+ line_len);
+ else
+ cursor_vis_width = get_str_width(vis.begin() + start,
+ line_len);
+ }
+ else {
+ // The cursor is inside the line; find the visual position of the
+ // cursor, then calculate the width of the segment.
+ int cursor_vis_line_pos = position_L_to_V[start + cursor_log_line_pos];
+ if (p.is_rtl())
+ cursor_vis_width = get_rev_str_width(
+ vis.begin() + start + cursor_vis_line_pos + 1,
+ line_len - cursor_vis_line_pos - 1);
+ else
+ cursor_vis_width = get_str_width(
+ vis.begin() + start,
+ cursor_vis_line_pos);
+ }
+
+ return cursor_vis_width;
+}
+
+// move_to_vis_column() - positions the cursor at a visual column.
+// It does a visual-to-logical conversion and then sums the widths of the
+// characters, in visual order, till it reachs the right column. then
+// it converts this visual column to a logical one.
+
+void EditBox::move_to_vis_column(int column)
+{
+ Paragraph &p = *curr_para();
+
+ // Find the start and the end of the screen line.
+ int inner_line = calc_inner_line();
+ idx_t start = (inner_line > 0)
+ ? p.line_breaks[inner_line - 1]
+ : 0;
+ idx_t end = p.line_breaks[inner_line];
+ idx_t line_len = end - start;
+
+ LevelsArray &levels = cache.levels;
+ IdxArray &position_V_to_L = cache.position_V_to_L;
+ unistring &vis = cache.vis;
+ // We apply the BiDi algorithm if the
+ // results are not already cached.
+ if (!cache.owned_by(cursor.para)) {
+ cache.invalidate(); // it's owned by someone else
+ levels.resize(p.str.len());
+ DBG(100, ("get_embedding_levels() - by move_to_vis_column()\n"));
+ BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
+ p.base_dir(), levels.begin(),
+ p.breaks_count(), p.line_breaks.begin(),
+ !bidi_enabled);
+ vis = p.str;
+ position_V_to_L.resize(p.str.len());
+ // reorder just the screen line.
+ reorder(levels.begin() + start, line_len,
+ position_V_to_L.begin() + start, NULL, vis.begin() + start);
+ }
+
+ if (p.is_rtl()) {
+ // revrse the visual string so that we can use the same loop
+ // for both RTL and LTR lines.
+ reverse(vis.begin() + start, line_len);
+ }
+
+ // sum the widths of the charaters, in visual order, till we reach
+ // the right [visual] column.
+ int cursor_vis_line_pos = 0; // silence the compiler
+ int width = 0;
+ idx_t i;
+ wdstate stt;
+ for (i = 0; i < line_len; i++) {
+ width += get_char_width(vis[start + i], width, &stt, !p.is_rtl());
+ if (width > column) {
+ cursor_vis_line_pos = i;
+ break;
+ }
+ }
+
+ if (p.is_rtl()) {
+ // cancel the reverse() we did, because the cache is reused latter.
+ reverse(vis.begin() + start, line_len);
+ }
+
+ // find the logical column corresponding to the visual column.
+ int cursor_log_line_pos;
+
+ if (i == line_len) { // cursor at end of line?
+ if (inner_line == p.breaks_count() - 1) { // the last inner line?
+ // yes, stand past end of line
+ cursor_log_line_pos = cursor_vis_line_pos = line_len;
+ } else {
+ // no, stand on the last char of the line
+ cursor_log_line_pos = cursor_vis_line_pos = line_len - 1;
+ }
+ } else {
+ // use the V_to_L mapping to find the logical column.
+ if (p.is_rtl()) {
+ cursor_vis_line_pos = line_len - cursor_vis_line_pos - 1;
+ cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
+ } else {
+ cursor_log_line_pos = position_V_to_L[start + cursor_vis_line_pos];
+ }
+ }
+
+ cursor.pos = start + cursor_log_line_pos;
+}
+
+int EditBox::get_text_width() const
+{
+ if (!terminal::is_interactive())
+ return non_interactive_text_width;
+ else
+ return window_width() - margin_before - margin_after;
+}
+
+// wrap_para() - wraps a paragraph. that means to populate the line_breaks
+// array. this method is called when a paragraph has changed or when various
+// display options that affect the width of some characters are changed.
+
+void EditBox::wrap_para(Paragraph &para)
+{
+ para.line_breaks.clear();
+
+ if (wrap_type == wrpOff) {
+ para.line_breaks.push_back(para.str.len());
+ return;
+ }
+
+ int visible_text_width = get_text_width();
+ int line_width = 0;
+ idx_t line_len = 0;
+
+ wdstate stt;
+ for (idx_t i = 0; i < para.str.len(); i++) {
+ int char_width = get_char_width(para.str[i], line_width, &stt);
+ if ( (line_width + char_width > visible_text_width && line_len > 0)
+ || para.str[i] == UNICODE_LS )
+ {
+ if (para.str[i] == UNICODE_LS) {
+ ; // do nothing: break after LS; don't trace back to wspace
+ } else if (wrap_type == wrpAtWhiteSpace) {
+ // avoid breaking words: break at the previous wspace
+ idx_t saved_i = i;
+ while (line_len > 0
+ && (!BiDi::is_space(para.str[i])
+ || i == para.str.len() - 1)) {
+ i--;
+ line_len--;
+ }
+ if (line_len == 0) {
+ // no wspace found; we have to break the word
+ i = saved_i - 1;
+ }
+ } else { // wrpAnywhere
+ i--;
+ }
+ para.line_breaks.push_back(i + 1);
+ line_len = 0;
+ line_width = 0;
+ stt.clear();
+ } else {
+ line_len++;
+ line_width += char_width;
+ }
+ }
+
+ // add the end-of-paragraph to line_breaks.
+ // first make sure it's not already there (e.g. when the paragraph
+ // terminates in a LS).
+ if (para.line_breaks.empty() || para.line_breaks.back() != para.str.len())
+ para.line_breaks.push_back(para.str.len());
+}
+
+void EditBox::rewrap_all()
+{
+ for (int i = 0; i < parags_count(); i++)
+ wrap_para(*paragraphs[i]);
+ if (wrap_type == wrpOff) {
+ // if we've just turned wrap off, make sure the top inner line is 0.
+ top_line.inner_line = 0;
+ }
+ scroll_to_cursor_line();
+ request_update(rgnAll);
+}
+
+void EditBox::reformat()
+{
+ rewrap_all();
+}
+
+void EditBox::set_wrap_type(WrapType value)
+{
+ wrap_type = value;
+ rewrap_all();
+ NOTIFY_CHANGE(wrap);
+}
+
+INTERACTIVE void EditBox::toggle_wrap()
+{
+ switch (wrap_type) {
+ case wrpOff: set_wrap_type(wrpAtWhiteSpace); break;
+ case wrpAtWhiteSpace: set_wrap_type(wrpAnywhere); break;
+ case wrpAnywhere: set_wrap_type(wrpOff); break;
+ }
+}
+
+void EditBox::set_tab_width(int value)
+{
+ if (value > 0) {
+ tab_width = value;
+ rewrap_all();
+ }
+}
+
+void EditBox::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ if (old_width != columns) { // has the width changed?
+ rewrap_all();
+ } else {
+ // No, no need to rewrap
+ scroll_to_cursor_line();
+ request_update(rgnAll);
+ }
+ old_width = columns;
+}
+
+// }}}
+
+// Syntax Highlighting {{{
+
+void EditBox::set_syn_hlt(syn_hlt_t syn)
+{
+ syn_hlt = syn;
+ request_update(rgnAll);
+}
+
+INTERACTIVE void EditBox::menu_set_syn_hlt_none()
+{
+ set_syn_hlt(synhltOff);
+}
+
+INTERACTIVE void EditBox::menu_set_syn_hlt_html()
+{
+ set_syn_hlt(synhltHTML);
+}
+
+INTERACTIVE void EditBox::menu_set_syn_hlt_email()
+{
+ set_syn_hlt(synhltEmail);
+}
+
+void EditBox::set_underline(bool v)
+{
+ underline_hlt = v;
+ request_update(rgnAll);
+}
+
+INTERACTIVE void EditBox::toggle_underline()
+{
+ set_underline(!get_underline());
+}
+
+// detect_syntax() tries to detect the syntax of the buffer (and turn on
+// the appropriate highlighting). If 2 or more lines starting with ">"
+// are found within the first 10 lines, it's assumed to be an email
+// message. If "<html" or "<HTML" is found within the first 5 lines, it's
+// assumed to be HTML.
+
+void EditBox::detect_syntax()
+{
+ syn_hlt_t syntax = synhltOff;
+
+ int quote_count = 0;
+ for (int i = 0; i < 10 && i < parags_count(); i++) {
+ const unistring &str = paragraphs[i]->str;
+ idx_t len = str.len();
+ idx_t pos = 0;
+ while (pos < len && str[pos] == ' ')
+ pos++;
+ if (pos < len && str[pos] == '>')
+ quote_count++;
+ }
+
+ if (quote_count >= 2) {
+ syntax = synhltEmail;
+ } else {
+ for (int i = 0; i < 5 && i < parags_count(); i++) {
+ if ((paragraphs[i]->str.index(u8string("<html")) != -1) ||
+ (paragraphs[i]->str.index(u8string("<HTML")) != -1))
+ {
+ syntax = synhltHTML;
+ break;
+ }
+ }
+ }
+
+ set_syn_hlt(syntax);
+}
+
+// Highlight HTML
+
+static void highlight_html(const unistring &str, EditBox::AttributeArray& attributes)
+{
+ idx_t len = str.len();
+ attribute_t html_attr = get_attr(EDIT_HTML_TAG_ATTR);
+ bool is_color = contains_color(html_attr);
+ bool in_tag = false;
+
+ for (idx_t pos = 0; pos < len; pos++) {
+ if (str[pos] == '<')
+ in_tag = true;
+ if (in_tag) {
+ if (is_color)
+ attributes[pos] = html_attr;
+ else
+ attributes[pos] |= html_attr;
+ }
+ if (str[pos] == '>')
+ in_tag = false;
+ }
+}
+
+// Highlight Email
+
+#define MAX_QUOTE_LEVEL 9
+static void highlight_email(const unistring &str, EditBox::AttributeArray& attributes)
+{
+ idx_t len = str.len();
+ attribute_t quote_attr = A_NORMAL; // silence the compiler.
+ bool is_color = false;
+ idx_t pos = 0;
+ int level = 0;
+
+ while (pos < len && str[pos] == ' ')
+ pos++;
+ if (pos < len && str[pos] == '>')
+ while (pos < len) {
+ if (str[pos] == '>' && level < MAX_QUOTE_LEVEL) {
+ level++;
+ quote_attr = get_attr(EDIT_EMAIL_QUOTE1_ATTR + level - 1);
+ is_color = contains_color(quote_attr);
+ }
+ if (is_color)
+ attributes[pos] = quote_attr;
+ else
+ attributes[pos] |= quote_attr;
+ pos++;
+ }
+}
+
+inline bool is_ltr_alpha(unichar ch)
+{
+ return BiDi::is_alnum(ch) && !BiDi::is_rtl(ch);
+}
+
+// Highlight underline (*text* and _text_)
+
+static void highlight_underline(const unistring &str, EditBox::AttributeArray& attributes)
+{
+ idx_t len = str.len();
+ attribute_t underline_attr = get_attr(EDIT_EMPHASIZED_ATTR);
+ bool is_color = contains_color(underline_attr);
+ bool in_emph = false;
+
+ for (idx_t pos = 0; pos < len; pos++) {
+ bool emph_ch = (str[pos] == '_' || str[pos] == '*');
+ if (emph_ch) {
+ // ignore "http://host/show_bug.cgi" and "ticks_per_second" by making
+ // sure it's not LTR alphanumerics on both sides.
+ if (pos > 0 && is_ltr_alpha(str[pos-1]) &&
+ pos < len-1 && is_ltr_alpha(str[pos+1]))
+ emph_ch = false;
+ }
+ if (emph_ch)
+ in_emph = !in_emph;
+ if (in_emph || emph_ch) {
+ if (is_color)
+ attributes[pos] = underline_attr;
+ else
+ attributes[pos] |= underline_attr;
+ }
+ }
+}
+
+// }}}
+
+// low-level drawing {{{
+
+// get_char_repr() - get representation of some undisplayable characters,
+// EOPs, and of some interface elements (e.g. '$').
+
+unichar EditBox::get_char_repr(unichar ch)
+{
+ if (reprtbl.translate_char(ch)) {
+ return ch;
+ } else {
+ if (reprtbl.empty())
+ return 'X';
+ else
+ return ch;
+ }
+}
+
+// draw_unistr() - draws an LTR visual string.
+
+// latin1_transliterate() - when not in UTF-8 locale, we transliterate
+// some Unicode characters to make them readable.
+
+static unichar latin1_transliterate(unichar ch)
+{
+ switch (ch) {
+ // Note: 0xB4 (SPACING ACCENT) and 0xA8 (SPACING DIARESIS) cause
+ // some troubles on my Linux console when not in UTF-8 locale, probably
+ // because the console driver erroneously thinks it's a non-spacing
+ // mark (as U+0301 and U+0308 are).
+ case 0xB4:
+ case 0xA8:
+ return 'x';
+
+ case 0x203E:
+ return 0xAF; // OVERLINE (we transliterate to "macron" because
+ // it's similar)
+
+ case UNI_HEB_MAQAF:
+ case UNI_HYPHEN:
+ case UNI_NON_BREAKING_HYPHEN:
+ case UNI_EN_DASH:
+ case UNI_EM_DASH:
+ case UNI_MINUS_SIGN:
+ return '-';
+
+ case UNI_HEB_GERESH:
+ case UNI_RIGHT_SINGLE_QUOTE:
+ case UNI_SINGLE_LOW9_QUOTE:
+ case UNI_SINGLE_HIGH_REV9_QUOTE:
+ return '\'';
+
+ case UNI_LEFT_SINGLE_QUOTE:
+ return '`';
+
+ case UNI_HEB_GERSHAYIM:
+ case UNI_LEFT_DOUBLE_QUOTE:
+ case UNI_RIGHT_DOUBLE_QUOTE:
+ case UNI_DOUBLE_LOW9_QUOTE:
+ case UNI_DOUBLE_HIGH_REV9_QUOTE:
+ return '"';
+
+ case UNI_HEB_SOF_PASUQ:
+ return ':';
+ case UNI_HEB_PASEQ:
+ return '|';
+ case UNI_BULLET:
+ return '*';
+ }
+ return ch;
+}
+
+void EditBox::draw_unistr(const unichar *str, idx_t len,
+ attribute_t *attributes, int *tab_widths)
+{
+ unistring buf;
+ if (terminal::do_arabic_shaping) {
+ buf.append(str, len);
+ len = shape(buf.begin(), len, attributes);
+ buf.resize(len);
+ str = buf.begin();
+ }
+
+#define SETWATTR(_attr) \
+ if (current_attr != int(_attr)) { \
+ wattrset(wnd, _attr); \
+ current_attr = _attr; \
+ }
+
+#ifdef HAVE_WIDE_CURSES
+#define put_unichar_attr(_wch, _attr) \
+ do { \
+ unichar wch = _wch; \
+ if (!terminal::is_utf8) { \
+ wch = latin1_transliterate(wch); \
+ if (WCTOB(wch) == EOF) { \
+ wch = get_char_repr(FAILED_CONV_REPR); \
+ /*def_attr |= FAILED_CONV_COLOR;*/ \
+ if (contains_color(def_attr)) \
+ def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
+ else \
+ def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
+ } \
+ } \
+ /*SETWATTR(_attr | def_attr);*/ \
+ SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
+ waddnwstr(wnd, (wchar_t*)&wch, 1); \
+ } while (0)
+#else
+#define put_unichar_attr(_wch, _attr) \
+ do { \
+ int ich = latin1_transliterate(_wch); \
+ ich = terminal::force_iso88598 ? unicode_to_iso88598(ich) : WCTOB(ich); \
+ if (ich == EOF) { \
+ ich = get_char_repr(FAILED_CONV_REPR); \
+ /*def_attr |= FAILED_CONV_COLOR;*/ \
+ if (contains_color(def_attr)) \
+ def_attr = get_attr(EDIT_FAILED_CONV_ATTR); \
+ else \
+ def_attr |= get_attr(EDIT_FAILED_CONV_ATTR); \
+ } \
+ /*SETWATTR(_attr | def_attr);*/ \
+ SETWATTR(contains_color(def_attr) ? def_attr : (_attr | def_attr)); \
+ waddch(wnd, (unsigned char)ich); \
+ } while (0)
+#endif
+
+ int tab_counter = 0;
+ int line_width = 0;
+ int current_attr = -1;
+
+ for (idx_t i = 0; i < len; i++) {
+ unichar ch = str[i];
+ int char_width = IS_SIMPLE_ONE_COLUMN_CHAR(ch) ? 1 : mk_wcwidth(ch);
+ int def_attr = attributes ? attributes[i] : A_NORMAL;
+
+ if (char_width < 1) {
+ if (ch == '\t') {
+ if (tab_widths)
+ char_width = tab_widths[tab_counter++];
+ else
+ char_width = tab_width - (line_width % tab_width);
+ for (int t = 0; t < char_width; t++) {
+ if (show_tabs)
+ put_unichar_attr(get_char_repr('\t'), get_attr(EDIT_TAB_ATTR));
+ else
+ put_unichar_attr(' ', 0);
+ }
+ } else if (char_width == -1 || ch == '\0') {
+ // Control characters / nulls: print symbol instead
+ char_width = 1;
+ put_unichar_attr(get_char_repr(CONTROL_REPR), get_attr(EDIT_CONTROL_ATTR));
+ } else {
+ // Non-Spacing Marks.
+ // First, handle BIDI explicits and Hebrew NSMs
+ if (BiDi::is_explicit_mark(ch)) {
+ if (show_explicits) {
+ char_width = 1;
+ put_unichar_attr(get_char_repr(ch), get_attr(EDIT_EXPLICIT_ATTR));
+ }
+ } else if (BiDi::is_hebrew_nsm(ch) || BiDi::is_arabic_nsm(ch)) {
+ if (rtl_nsm_display == rtlnsmTransliterated) {
+ char_width = 1;
+ put_unichar_attr(get_char_repr(ch),
+ BiDi::is_cantillation_nsm(ch)
+ ? get_attr(EDIT_NSM_CANTILLATION_ATTR)
+ : (BiDi::is_arabic_nsm(ch)
+ ? get_attr(EDIT_NSM_ARABIC_ATTR)
+ : get_attr(EDIT_NSM_HEBREW_ATTR)) );
+ } else if (rtl_nsm_display == rtlnsmAsis
+ && terminal::is_utf8 && !terminal::is_fixed) {
+ put_unichar_attr(ch, 0);
+ }
+ } else {
+ // Now handle general NSMs
+ if (!terminal::is_utf8 || terminal::is_fixed) {
+ char_width = 1;
+ put_unichar_attr(get_char_repr(NSM_REPR), get_attr(EDIT_NSM_ATTR));
+ } else {
+ // :TODO: new func: is_printable_zw()
+ if (ch != UNI_ZWNJ && ch != UNI_ZWJ) {
+ put_unichar_attr(ch, 0);
+ }
+ }
+ }
+ }
+ } else {
+ if (char_width == 2 && (!terminal::is_utf8 || terminal::is_fixed)) {
+ // Wide Asian ideograph, but terminal is not capable
+ char_width = 1;
+ put_unichar_attr(get_char_repr(WIDE_REPR), get_attr(EDIT_WIDE_ATTR));
+ } else {
+ attribute_t xattr = 0;
+ if (ch == UNICODE_LS) {
+ ch = get_char_repr(UNICODE_LS);
+ xattr = get_attr(EDIT_UNICODE_LS_ATTR);
+ } else if (ch == UNI_NO_BREAK_SPACE) {
+ ch = ' ';
+ } else if (ch == UNI_HEB_MAQAF) {
+ if (maqaf_display == mqfTransliterated
+ || maqaf_display == mqfHighlighted)
+ ch = get_char_repr(UNI_HEB_MAQAF);
+ if (maqaf_display == mqfHighlighted)
+ xattr = get_attr(EDIT_MAQAF_ATTR);
+ }
+
+ put_unichar_attr(ch, xattr);
+ }
+ }
+ line_width += char_width;
+ }
+ SETWATTR(A_NORMAL);
+#undef SETWATTR
+#undef put_unichar_attr
+}
+
+void EditBox::calc_tab_widths(const unichar *str, idx_t len,
+ bool rev, IntArray &tab_widths)
+{
+ tab_widths.clear();
+ int line_width = 0;
+ wdstate stt;
+ idx_t i = rev ? len - 1 : 0;
+ while (len--) {
+ int char_width = get_char_width(str[i], line_width, &stt);
+ if (str[i] == '\t') {
+ if (rev)
+ tab_widths.insert(tab_widths.begin(), char_width);
+ else
+ tab_widths.push_back(char_width);
+ }
+ line_width += char_width;
+ i += rev ? -1 : 1;
+ }
+}
+
+// draw_rev_unistr() - draws an RTL visual string. it calls draw_string()
+// to do the actuall work. The two functions differ in how they
+// calculate TAB widths. draw_rev_unistr() is neccessary because in RTL
+// visual strings the first column is at the end of the string. it
+// calculates the TAB widths accordingly and passes them to draw_unistring().
+
+void EditBox::draw_rev_unistr(const unichar *str, idx_t len,
+ attribute_t *attributes)
+{
+ IntArray tab_widths;
+ calc_tab_widths(str, len, true, tab_widths);
+ draw_unistr(str, len, attributes, tab_widths.begin());
+}
+
+void EditBox::put_unichar_attr_at(int line, int col, unichar ch, int attr)
+{
+ wattrset(wnd, attr);
+ wmove(wnd, line, col);
+ put_unichar(ch, get_char_repr(FAILED_CONV_REPR));
+}
+
+// draw_eop() - draws an EOP symbol.
+
+void EditBox::draw_eop(int y, int x, Paragraph &p, bool selected)
+{
+ if (!show_paragraph_endings)
+ return;
+
+ unichar ch;
+ switch (p.eop) {
+ case eopMac: ch = EOP_MAC_REPR; break;
+ case eopDOS: ch = EOP_DOS_REPR; break;
+ case eopUnix: ch = p.is_rtl()
+ ? EOP_UNIX_RTL_REPR
+ : EOP_UNIX_LTR_REPR;
+ break;
+ case eopUnicode:
+ ch = EOP_UNICODE_REPR; break;
+ default:
+ ch = EOP_NONE_REPR;
+ }
+ put_unichar_attr_at(y, x, get_char_repr(ch),
+ get_attr(EDIT_EOP_ATTR) | (selected ? A_REVERSE : 0));
+}
+
+// }}}
+
+// redraw_paragraph {{{
+
+void EditBox::redraw_unwrapped_paragraph(
+ Paragraph &p,
+ int window_start_line,
+ bool only_cursor,
+ int para_num,
+ LevelsArray &levels,
+ IdxArray& position_L_to_V,
+ IdxArray& position_V_to_L,
+ AttributeArray& attributes,
+ bool eop_is_selected
+ )
+{
+ unistring &vis = cache.vis;
+ if (!cache.owned_by(para_num)) {
+ vis = p.str;
+ reorder(levels.begin(), p.str.len(),
+ position_V_to_L.begin(),
+ position_L_to_V.begin(),
+ vis.begin(),
+ attributes.begin(), do_mirror,
+ (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
+ }
+
+ // Step 1. find out the start and end of the
+ // segment visible on screen.
+
+ // note: we use the term "col", or "pos", to refer to character index, and
+ // "width" to refer to the visual position on screen -- sum of the widths
+ // of the preceeding character.
+
+ // find the visual cursor index
+ int cursor_vis_width = 0;
+ idx_t cursor_vis_line_pos = -1;
+
+ if (curr_para() == &p) {
+ if (cursor.pos == p.str.len())
+ cursor_vis_line_pos = p.str.len();
+ else {
+ cursor_vis_line_pos = position_L_to_V[cursor.pos];
+ if (p.is_rtl())
+ cursor_vis_line_pos = p.str.len() - cursor_vis_line_pos - 1;
+ }
+ }
+
+ idx_t start_col = 0, end_col = 0;
+ int visible_text_width = get_text_width();
+ int segment_width = 0;
+
+ if (p.is_rtl()) {
+ reverse(vis.begin(), p.str.len());
+ reverse(attributes.begin(), p.str.len());
+ }
+
+ idx_t i;
+ wdstate stt;
+ for (i = 0; i < p.str.len(); i++) {
+ int char_width = get_char_width(vis[i], segment_width, &stt, !p.is_rtl());
+ segment_width += char_width;
+
+ if (segment_width > visible_text_width) {
+ if (cursor_vis_line_pos < i) {
+ // we've found the end of the segment.
+ segment_width -= char_width;
+ end_col = i;
+ break;
+ } else {
+ // start a new segment. recalculate the width of this
+ // first character because TAB's width may change.
+ stt.clear();
+ char_width = get_char_width(vis[i], 0, &stt, !p.is_rtl());
+ segment_width = char_width;
+ start_col = i;
+ }
+ }
+ if (i == cursor_vis_line_pos)
+ cursor_vis_width = segment_width - char_width;
+ }
+ if (end_col == 0)
+ end_col = i;
+ if (cursor.pos == p.str.len()) {
+ cursor_vis_width = segment_width;
+ }
+
+ if (p.is_rtl()) {
+ reverse(vis.begin() + start_col, end_col - start_col);
+ reverse(attributes.begin() + start_col, end_col - start_col);
+ }
+
+ // Step 2. draw the segment [start_col .. end_col)
+
+ if (p.is_rtl()) {
+ wmove(wnd, window_start_line,
+ margin_after + visible_text_width - segment_width);
+ draw_rev_unistr(vis.begin() + start_col, end_col - start_col,
+ attributes.begin() + start_col);
+ } else {
+ wmove(wnd, window_start_line, margin_before);
+ draw_unistr(vis.begin() + start_col, end_col - start_col,
+ attributes.begin() + start_col);
+ }
+
+ if (p.is_rtl()) {
+ // cancel the reverse() we did, because the cache is reused latter.
+ reverse(vis.begin() + start_col, end_col - start_col);
+ reverse(vis.begin(), p.str.len());
+ }
+
+ // Step 3. draw EOP / continuation indicator
+ if (end_col != p.str.len()) {
+ // if the end of the para is not shown,
+ // draw line-countinuation indicator ('$')
+ put_unichar_attr_at(
+ window_start_line,
+ p.is_rtl() ? margin_after - 1
+ : margin_before + visible_text_width,
+ get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
+ } else {
+ // if the end is shown, draw EOP
+ draw_eop(window_start_line,
+ p.is_rtl() ? margin_after + visible_text_width
+ - segment_width - 1
+ : margin_before + segment_width,
+ p, eop_is_selected);
+ }
+
+ if (start_col != 0 && cursor_vis_width > 2) {
+ // if the beginning of the para is not shown,
+ // draw another line-continuation indicator, at the other side.
+ put_unichar_attr_at(
+ window_start_line,
+ p.is_rtl() ? margin_after + visible_text_width - 1
+ : margin_before,
+ get_char_repr(TRIM_REPR), get_attr(EDIT_TRIM_ATTR));
+ }
+
+ // Step 4. position the cursor
+
+ if (p.is_rtl()) {
+ cursor_vis_width = visible_text_width - cursor_vis_width - 1;
+ wmove(wnd, window_start_line, margin_after + cursor_vis_width);
+ } else {
+ wmove(wnd, window_start_line, margin_before + cursor_vis_width);
+ }
+}
+
+void EditBox::redraw_wrapped_paragraph(
+ Paragraph &p,
+ int window_start_line,
+ bool only_cursor,
+ int para_num,
+ LevelsArray &levels,
+ IdxArray& position_L_to_V,
+ IdxArray& position_V_to_L,
+ AttributeArray& attributes,
+ bool eop_is_selected
+ )
+{
+ idx_t cursor_vis_line_pos = -1;
+ int cursor_vis_width = 0;
+ int cursor_line = -1;
+
+ unistring &vis = cache.vis;
+ if (!cache.owned_by(para_num))
+ vis = p.str;
+
+ int visible_text_width = get_text_width();
+ idx_t prev_line_break = 0;
+
+ // draw the paragraph line by line.
+ for (int line_num = 0;
+ line_num < p.breaks_count();
+ line_num++)
+ {
+ idx_t line_break = p.line_breaks[line_num];
+ idx_t line_len = line_break - prev_line_break;
+ bool is_last_line = (line_num == p.breaks_count() - 1);
+
+ if (!cache.owned_by(para_num)) {
+ reorder(levels.begin() + prev_line_break, line_len,
+ position_V_to_L.begin() + prev_line_break,
+ position_L_to_V.begin() + prev_line_break,
+ vis.begin() + prev_line_break,
+ attributes.begin() + prev_line_break, do_mirror,
+ (rtl_nsm_display == rtlnsmAsis) && do_mirror/*NSM reordering*/);
+ }
+
+ // draw segment [prev_line_break .. line_break)
+ if ( !only_cursor && window_start_line + line_num >= 0
+ && window_start_line + line_num < window_height() ) {
+ // :TODO: handle TABS at end of line (especially RTL lines)
+ if (p.is_rtl()) {
+ int line_width = get_rev_str_width(
+ vis.begin() + prev_line_break, line_len);
+ int window_x = margin_after;
+ // we reserve one space for wrap (margin_after), so:
+ if (line_width > visible_text_width)
+ window_x--;
+ else
+ window_x += visible_text_width - line_width;
+ wmove(wnd, window_start_line + line_num, window_x);
+ draw_rev_unistr(vis.begin() + prev_line_break, line_len,
+ attributes.begin() + prev_line_break);
+ if (is_last_line) {
+ draw_eop(window_start_line + line_num, window_x - 1,
+ p, eop_is_selected);
+ } else if (wrap_type == wrpAnywhere) {
+ put_unichar_attr_at(
+ window_start_line + line_num,
+ margin_after - 1,
+ get_char_repr(WRAP_RTL_REPR), get_attr(EDIT_WRAP_ATTR));
+ }
+ } else {
+ wmove(wnd, window_start_line + line_num, margin_before);
+ draw_unistr(vis.begin() + prev_line_break, line_len,
+ attributes.begin() + prev_line_break);
+ if (is_last_line) {
+ draw_eop(window_start_line + line_num,
+ margin_before + get_str_width(vis.begin()
+ + prev_line_break, line_len),
+ p, eop_is_selected);
+ } else if (wrap_type == wrpAnywhere) {
+ put_unichar_attr_at(
+ window_start_line + line_num,
+ margin_before + visible_text_width,
+ get_char_repr(WRAP_LTR_REPR), get_attr(EDIT_WRAP_ATTR));
+ }
+ }
+ }
+
+ // find the visual cursor position
+ if (curr_para() == &p && cursor_line == -1) {
+ if (cursor.pos < line_break) {
+ int cursor_log_line_pos = cursor.pos - prev_line_break;
+ cursor_vis_line_pos = position_L_to_V[
+ prev_line_break + cursor_log_line_pos];
+ if (p.is_rtl()) {
+ cursor_vis_width = get_rev_str_width(
+ vis.begin() + prev_line_break
+ + cursor_vis_line_pos + 1,
+ line_len - cursor_vis_line_pos - 1);
+ } else {
+ cursor_vis_width = get_str_width(
+ vis.begin() + prev_line_break,
+ cursor_vis_line_pos);
+ }
+ cursor_line = line_num;
+ }
+ else if (cursor.pos == p.str.len() && is_last_line) {
+ if (p.is_rtl()) {
+ cursor_vis_width = get_rev_str_width(
+ vis.begin() + prev_line_break,
+ line_len);
+ } else {
+ cursor_vis_width = get_str_width(
+ vis.begin() + prev_line_break,
+ line_len);
+ }
+ cursor_line = line_num;
+ }
+ }
+
+ prev_line_break = line_break;
+ }
+
+ // position the cursor
+ if (cursor_line != -1) {
+ if (p.is_rtl()) {
+ cursor_vis_width = visible_text_width - cursor_vis_width - 1;
+ wmove(wnd,
+ window_start_line + cursor_line,
+ margin_after + cursor_vis_width);
+ } else {
+ wmove(wnd,
+ window_start_line + cursor_line,
+ margin_before + cursor_vis_width);
+ }
+ }
+}
+
+void EditBox::do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num)
+{
+ switch (syn_hlt) {
+ case synhltHTML:
+ highlight_html(str, attributes);
+ break;
+ case synhltEmail:
+ highlight_email(str, attributes);
+ break;
+ default:
+ break;
+ }
+ if (underline_hlt)
+ highlight_underline(str, attributes);
+}
+
+void EditBox::redraw_paragraph(Paragraph &p, int window_start_line,
+ bool only_cursor, int para_num)
+{
+ //if (is_primary_mark_set()
+ //cache.invalidate();
+
+ if (is_primary_mark_set()
+ || wrap_type == wrpOff) // FIXME: when wrap=off, highlighting fucks up in RTL lines;
+ // cache.invalidate() is a temporary solution.
+ cache.invalidate();
+
+ LevelsArray &levels = cache.levels;
+ IdxArray &position_L_to_V = cache.position_L_to_V;
+ IdxArray &position_V_to_L = cache.position_V_to_L;
+ AttributeArray &attributes = cache.attributes;
+
+ if (!cache.owned_by(para_num)) {
+ position_L_to_V.resize(p.str.len());
+ position_V_to_L.resize(p.str.len());
+ levels.resize(p.str.len());
+ attributes.clear();
+ attributes.resize(p.str.len());
+ DBG(100, ("get_embedding_levels() - by redraw_paragraph()\n"));
+ BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
+ p.base_dir(), levels.begin(),
+ p.breaks_count(), p.line_breaks.begin(),
+ !bidi_enabled);
+ do_syntax_highlight(p.str, attributes, para_num);
+ }
+
+ //AttributeArray attributes(p.str.len());
+ bool eop_is_selected = false;
+
+ // We use the "attributes" array to highlight the selection.
+ // for each selected characetr, we "OR" the corresponding
+ // attribute element with A_REVERSE.
+ if (is_primary_mark_set()) {
+ Point lo = primary_mark, hi = cursor;
+ if (hi < lo)
+ hi.swap(lo);
+ if (lo.para <= para_num && hi.para >= para_num) {
+ idx_t start, end;
+ if (lo.para == para_num)
+ start = lo.pos;
+ else
+ start = 0;
+ if (hi.para == para_num)
+ end = hi.pos;
+ else {
+ eop_is_selected = true;
+ end = p.str.len();
+ }
+
+ attribute_t selected_attr = get_attr(EDIT_SELECTED_ATTR);
+ idx_t i;
+ if (contains_color(selected_attr))
+ for (i = start; i < end; i++)
+ attributes[i] = selected_attr;
+ else
+ for (i = start; i < end; i++)
+ attributes[i] |= selected_attr;
+ }
+ }
+
+ if (wrap_type == wrpOff) {
+ redraw_unwrapped_paragraph(
+ p,
+ window_start_line,
+ only_cursor,
+ para_num,
+ levels,
+ position_L_to_V,
+ position_V_to_L,
+ attributes,
+ eop_is_selected
+ );
+ } else {
+ redraw_wrapped_paragraph(
+ p,
+ window_start_line,
+ only_cursor,
+ para_num,
+ levels,
+ position_L_to_V,
+ position_V_to_L,
+ attributes,
+ eop_is_selected
+ );
+ }
+
+ cache.set_owner(para_num);
+}
+
+// }}}
+
+// Log2Vis {{{
+
+// emph_string() - emphasize marked-up segments. I.e. converts
+// "the *quick* fox" to "the q_u_i_c_k_ fox". but "* bullet"
+// stays "* bullet".
+
+static void emph_string(unistring &str,
+ unichar emph_marker, unichar emph_ch)
+{
+ bool in_emph = false;
+ for (int i = 0; i < str.len(); i++) {
+ if ( (!emph_marker && (str[i] == '*' || str[i] == '_')) ||
+ ( emph_marker && str[i] == emph_marker ) ) {
+ if (!(!in_emph &&
+ i < str.len()-1 &&
+ BiDi::is_space(str[i+1])) )
+ {
+ in_emph = !in_emph;
+ str.erase(str.begin() + i);
+ i--;
+ }
+ } else {
+ if (in_emph) {
+ i++;
+ // skip all NSMs.
+ while (i < str.len() && BiDi::is_nsm(str[i]))
+ i++;
+ str.insert(str.begin()+i, emph_ch);
+ }
+ }
+ }
+}
+
+// log2vis() - convert the [logical] document into visual.
+
+void EditBox::log2vis(const char *options)
+{
+ bool opt_bdo = false;
+ bool opt_nopad = false;
+ bool opt_engpad = false;
+
+ unichar opt_emph = false;
+ unichar opt_emph_marker = 0;
+ unichar opt_emph_ch = UNI_NS_UNDERSCORE;
+
+ // parse the 'options' string.
+ if (options) {
+ if (strstr(options, "bdo"))
+ opt_bdo = true;
+ if (strstr(options, "nopad"))
+ opt_nopad = true;
+ if (strstr(options, "engpad"))
+ opt_engpad = true;
+ char *s;
+ if ((s = strstr(options, "emph"))) {
+ opt_emph = true;
+ if (s[4] == ':') {
+ opt_emph_ch = strtol(s + 5, &s, 0);
+ if (s && (*s == ':' || *s == ','))
+ opt_emph_marker = strtol(s + 1, NULL, 0);
+ }
+ DBG(1, ("--EMPH-- glyph: U+%04lX, marker: U+%04lX\n",
+ (unsigned long)opt_emph_ch,
+ (unsigned long)opt_emph_marker));
+ }
+ }
+
+ std::vector<unistring> visuals;
+
+ for (int i = 0; i < parags_count(); i++)
+ {
+ Paragraph &p = *paragraphs[i];
+
+ unistring &visp = p.str;
+ if (opt_emph) {
+ emph_string(visp, opt_emph_marker, opt_emph_ch);
+ post_para_modification(p);
+ }
+
+ cache.invalidate();
+ LevelsArray &levels = cache.levels;
+ IdxArray &position_L_to_V = cache.position_L_to_V;
+ IdxArray &position_V_to_L = cache.position_V_to_L;
+
+ position_L_to_V.resize(p.str.len());
+ position_V_to_L.resize(p.str.len());
+ levels.resize(p.str.len());
+ DBG(100, ("get_embedding_levels() - by log2vis()\n"));
+ BiDi::get_embedding_levels(p.str.begin(), p.str.len(),
+ p.base_dir(), levels.begin(),
+ p.breaks_count(), p.line_breaks.begin());
+
+ int visible_text_width = get_text_width();
+ idx_t prev_line_break = 0;
+
+ for (int line_num = 0;
+ line_num < p.breaks_count();
+ line_num++)
+ {
+ idx_t line_break = p.line_breaks[line_num];
+ idx_t line_len = line_break - prev_line_break;
+
+ // trim
+ while (line_len
+ && BiDi::is_space(p.str[prev_line_break + line_len - 1]))
+ line_len--;
+
+ // convert to visual
+ reorder(levels.begin() + prev_line_break, line_len,
+ position_V_to_L.begin() + prev_line_break,
+ position_L_to_V.begin() + prev_line_break,
+ visp.begin() + prev_line_break,
+ NULL, true,
+ rtl_nsm_display == rtlnsmAsis);
+
+ if (terminal::do_arabic_shaping)
+ line_len = shape(p.str.begin() + prev_line_break,
+ line_len, NULL);
+
+ unistring visline;
+ // pad RTL lines
+ if (p.is_rtl() && !opt_nopad) {
+ int line_width = get_rev_str_width(
+ visp.begin() + prev_line_break, line_len);
+ while (line_width < visible_text_width) {
+ visline.push_back(' ');
+ line_width++;
+ }
+ }
+
+ // convert TABs to spaces, and convert/erase some
+ // special chars.
+ IntArray tab_widths;
+ int tab_counter = 0;
+ calc_tab_widths(visp.begin() + prev_line_break, line_len,
+ p.is_rtl(), tab_widths);
+ for (int i = prev_line_break; i < prev_line_break + line_len; i++) {
+ if (visp[i] == '\t') {
+ int j = tab_widths[tab_counter++];
+ while (j--)
+ visline.push_back(' ');
+ } else if (visp[i] == UNI_HEB_MAQAF
+ && maqaf_display != mqfAsis) {
+ visline.push_back('-');
+ } else {
+ if (!BiDi::is_transparent_formatting_code(visp[i]))
+ visline.push_back(visp[i]);
+ }
+ }
+
+ // pad LTR lines
+ if (!p.is_rtl() && opt_engpad) {
+ int line_width = get_str_width(
+ visp.begin() + prev_line_break, line_len);
+ while (line_width < visible_text_width) {
+ visline.push_back(' ');
+ line_width++;
+ }
+ }
+
+ if (opt_bdo) {
+ visline.insert(visline.begin(), UNI_LRO);
+ visline.push_back(UNI_PDF);
+ }
+ visuals.push_back(visline);
+
+ prev_line_break = line_break;
+ }
+
+ delete paragraphs[i];
+ }
+
+ // replace the logical document with the visual version.
+ paragraphs.clear();
+ for (int i = 0; i < (int)visuals.size(); i++) {
+ Paragraph *p = new Paragraph();
+ p->str = visuals[i];
+ p->eop = eopUnix;
+ post_para_modification(*p);
+ // skip the last empty line.
+ if (!(i == (int)visuals.size() - 1 && p->str.len() == 0))
+ paragraphs.push_back(p);
+ }
+
+ undo_stack.clear();
+ unset_primary_mark();
+ set_modified(true);
+ cursor.zero();
+ scroll_to_cursor_line();
+ request_update(rgnAll);
+}
+
+// }}}
+
diff --git a/editor.cc b/editor.cc
new file mode 100644
index 0000000..6ab707e
--- /dev/null
+++ b/editor.cc
@@ -0,0 +1,1103 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h> // chdir
+#include <sys/wait.h> // WEXITSTATUS()
+#include <string.h>
+
+#include "editor.h"
+#include "menus.h"
+#include "scrollbar.h"
+#include "io.h"
+#include "pathnames.h"
+#include "themes.h"
+#include "utf8.h" // used in show_character_code()
+#include "dbg.h"
+#include "transtbl.h"
+#include "helpbox.h"
+
+Editor *Editor::global_instance; // for SIGHUP
+
+#define LOAD_HISTORY 1
+#define SAVEAS_HISTORY 0
+#define SEARCH_HISTORY 2
+#define INSERT_HISTORY 1
+#define CHDIR_HISTORY 4
+#define WRITESELECTION_HISTORY 5
+#define SPELER_CMD_HISTORY 6
+#define SPELER_ENCODING_HISTORY 7
+#define EXTERNALEDITOR_HISTORY 8
+
+// describe_key() - reads a key event from the keyboard, searches and then
+// prints the description of the correspoding action.
+
+INTERACTIVE void Editor::describe_key()
+{
+ dialog.show_message(_("Describe key:"));
+ dialog.immediate_update();
+
+ Event evt;
+ get_next_event(evt, dialog.wnd);
+
+ // first look for a corresponding action in Editor's action map.
+ // If none was found, look in EditBox's.
+ const char *action, *desc = NULL;
+ if ((action = get_event_action(evt)))
+ desc = get_action_description(action);
+ else {
+ if ((action = wedit.get_event_action(evt)))
+ desc = wedit.get_action_description(action);
+ }
+ dialog.show_message(desc ? _(desc)
+ : _("Sorry, no description for this key"));
+}
+
+INTERACTIVE void Editor::refresh_and_center()
+{
+ wedit.center_line();
+ refresh();
+}
+
+void Editor::refresh(bool soft)
+{
+ if (!soft)
+ clearok(curscr, TRUE);
+ if (spellerwnd)
+ spellerwnd->invalidate_view();
+ if (scrollbar)
+ scrollbar->invalidate_view();
+ if (menubar)
+ menubar->invalidate_view();
+ wedit.invalidate_view();
+ status.invalidate_view();
+ dialog.invalidate_view();
+ update_terminal(soft);
+}
+
+// show_character_code() - prints the code value and the UTF-8 sequence
+// of the character on which the cursor stands.
+
+INTERACTIVE void Editor::show_character_code()
+{
+ unichar ch = wedit.get_current_char();
+
+ // construct a nice utf-8 representation in utf8_c_sync
+ char utf8[6], utf8_c_syn[6*4 + 1];
+ int nbytes = unicode_to_utf8(utf8, &ch, 1);
+ for (int i = 0; i < nbytes; i++)
+ sprintf(utf8_c_syn + i*4, "\\x%02X", (unsigned char)utf8[i]);
+
+ dialog.show_message_fmt(_("Unicode value: U+%04lX (decimal: %ld), "
+ "UTF-8 sequence: %s"),
+ (unsigned long)ch, (unsigned long)ch,
+ utf8_c_syn);
+}
+
+// show_character_info() - prints information from UnicodeData.txt
+// about the characetr on which the cursor stands.
+
+INTERACTIVE void Editor::show_character_info()
+{
+#define MAX_LINE_LEN 1024
+ unichar ch = wedit.get_current_char();
+
+ FILE *fp;
+ if ((fp = fopen(get_cfg_filename(USER_UNICODE_DATA_FILE), "r")) != NULL
+ || (fp = fopen(get_cfg_filename(SYSTEM_UNICODE_DATA_FILE), "r")) != NULL) {
+ char line[MAX_LINE_LEN];
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ if (strtol(line, NULL, 16) == (long)ch) {
+ line[strlen(line) - 1] = '\0'; // remove LF
+ dialog.show_message(line);
+ break;
+ }
+ }
+ fclose(fp);
+ } else {
+ set_last_error(errno);
+ show_file_io_error(_("Can't open %s: %s"),
+ get_cfg_filename(SYSTEM_UNICODE_DATA_FILE));
+ }
+#undef MAX_LINE_LEN
+}
+
+Editor::Editor()
+ : dialog(this),
+ status(this, &wedit),
+ speller(*this, dialog)
+{
+ spellerwnd = NULL;
+ wedit.set_error_listener(this);
+ global_instance = this;
+ set_default_encoding(DEFAULT_FILE_ENCODING);
+ set_encoding(get_default_encoding());
+ set_filename("");
+ set_new(false);
+#ifdef HAVE_CURS_SET
+ big_cursor = false;
+#endif
+
+ menubar = create_main_menubar(this, &wedit);
+ scrollbar = NULL;
+ scrollbar_pos = scrlbrNone;
+
+ // The rest is only needed if we're in interactive mode,
+ // that is, not in "do_log2vis" mode.
+ if (terminal::is_interactive()) {
+ layout_windows();
+ dialog.show_message_fmt(_("Welcome to %s version %s |"
+ "F1=Help, F9 or F10=Menu, ALT-X=Quit"),
+ PACKAGE, VERSION);
+
+ TranslationTable tbl;
+
+ if (tbl.load(get_cfg_filename(USER_TRANSTBL_FILE))
+ || tbl.load(get_cfg_filename(SYSTEM_TRANSTBL_FILE)))
+ wedit.set_translation_table(tbl);
+
+ if (tbl.load(get_cfg_filename(USER_REPRTBL_FILE))
+ || tbl.load(get_cfg_filename(SYSTEM_REPRTBL_FILE)))
+ wedit.set_repr_table(tbl);
+
+ if (tbl.load(get_cfg_filename(USER_ALTKBDTBL_FILE))
+ || tbl.load(get_cfg_filename(SYSTEM_ALTKBDTBL_FILE))) {
+ wedit.set_alt_kbd_table(tbl);
+ } else {
+ show_file_io_error(_("Can't load Hebrew kbd emulation %s: %s"),
+ get_cfg_filename(SYSTEM_ALTKBDTBL_FILE));
+ }
+ }
+}
+
+// show_file_io_error() - convenience method to print I/O errors.
+
+void Editor::show_file_io_error(const char *msg, const char *filename)
+{
+ u8string errmsg;
+ errmsg.cformat(msg, filename, get_last_error());
+ if (terminal::is_interactive())
+ dialog.show_error_message(errmsg.c_str());
+ else
+ fatal("%s\n", errmsg.c_str());
+}
+
+#ifdef HAVE_CURS_SET
+INTERACTIVE void Editor::toggle_big_cursor()
+{
+ set_big_cursor(!is_big_cursor());
+}
+#endif
+
+INTERACTIVE void Editor::toggle_cursor_position_report()
+{
+ status.toggle_cursor_position_report();
+}
+
+bool Editor::is_cursor_position_report() const
+{
+ return status.is_cursor_position_report();
+}
+
+// quit() - interactive command to quit the editor. it first makes sure the
+// user don't want to save the changes.
+
+INTERACTIVE void Editor::quit()
+{
+ if (wedit.is_modified()) {
+ bool canceled;
+ bool save = dialog.ask_yes_or_no(_("Buffer was modified; save changes?"),
+ &canceled);
+ if (canceled)
+ return;
+ if (save) {
+ save_file();
+ if (wedit.is_modified())
+ return;
+ }
+ }
+ finished = true; // signal exec()
+}
+
+// help() - interactive command to load and show the manual.
+
+void Editor::show_help_topic(const char *topic)
+{
+ HelpBox helpbox(this, wedit);
+ helpbox.layout_windows();
+ if (helpbox.load_user_manual()) {
+ if (topic)
+ helpbox.jump_to_topic(topic);
+ helpbox.exec();
+ refresh();
+ }
+}
+
+INTERACTIVE void Editor::help()
+{
+ show_help_topic(NULL);
+}
+
+void Editor::set_scrollbar_pos(scrollbar_pos_t pos)
+{
+ if (terminal::is_interactive()) {
+ if (pos == scrlbrNone) {
+ delete scrollbar;
+ scrollbar = NULL;
+ } else {
+ if (!scrollbar)
+ scrollbar = new Scrollbar();
+ wedit.sync_scrollbar(scrollbar);
+ }
+ scrollbar_pos = pos;
+ layout_windows();
+ }
+}
+
+INTERACTIVE void Editor::menu_set_scrollbar_none()
+{
+ set_scrollbar_pos(scrlbrNone);
+}
+
+INTERACTIVE void Editor::menu_set_scrollbar_left()
+{
+ set_scrollbar_pos(scrlbrLeft);
+}
+
+INTERACTIVE void Editor::menu_set_scrollbar_right()
+{
+ set_scrollbar_pos(scrlbrRight);
+}
+
+INTERACTIVE void Editor::menu()
+{
+ if (menubar)
+ menubar->exec();
+}
+
+bool Editor::save_file(const char *filename, const char *specified_encoding)
+{
+ status.invalidate_view(); // encoding may change, so update the statusline
+ unichar offending_char;
+ if (!xsave_file(&wedit, filename, specified_encoding,
+ get_backup_suffix(), offending_char)) {
+ show_file_io_error(_("Saving %s failed: %s"), filename);
+ if (offending_char)
+ wedit.move_first_char(offending_char);
+ return false;
+ } else {
+ set_filename(filename);
+ set_encoding(specified_encoding);
+ set_new(false);
+ wedit.set_modified(false);
+ dialog.show_message(_("Saved OK"));
+ return true;
+ }
+}
+
+bool Editor::write_selection_to_file(const char *filename,
+ const char *specified_encoding)
+{
+ unichar offending_char;
+ if (!xsave_file(&wedit, filename, specified_encoding,
+ get_backup_suffix(), offending_char, true)) {
+ show_file_io_error(_("Saving %s failed: %s"), filename);
+ if (offending_char)
+ wedit.move_first_char(offending_char);
+ return false;
+ } else {
+ dialog.show_message(_("Written OK"));
+ return true;
+ }
+}
+
+INTERACTIVE void Editor::write_selection_to_file()
+{
+ u8string qry_filename, qry_encoding;
+ if (query_filename(wedit.has_selected_text()
+ ? _("Write selection to:")
+ : _("Write whole text to:"),
+ qry_filename, qry_encoding, WRITESELECTION_HISTORY))
+ write_selection_to_file(qry_filename.c_str(),
+ qry_encoding.empty() ? get_encoding() : qry_encoding.c_str());
+}
+
+void Editor::set_encoding(const char *s)
+{
+ encoding = u8string(s);
+ status.invalidate_view();
+}
+
+bool Editor::load_file(const char *filename, const char *specified_encoding)
+{
+ bool is_new;
+ u8string effective_encoding;
+
+ status.invalidate_view();
+ set_filename("");
+
+ dialog.show_message(_("Loading..."));
+ dialog.immediate_update();
+ if (!xload_file(&wedit, filename, specified_encoding,
+ get_default_encoding(), effective_encoding, is_new, true)) {
+ if (!effective_encoding.empty())
+ set_encoding(effective_encoding.c_str());
+ set_new(false);
+ show_file_io_error(_("Loading %s failed: %s"), filename);
+ return false;
+ } else {
+ if (scrollbar)
+ wedit.sync_scrollbar(scrollbar);
+ set_filename(filename);
+ if (!is_new)
+ set_encoding(effective_encoding.c_str());
+ else
+ set_encoding(specified_encoding ? specified_encoding
+ : get_default_encoding());
+ set_new(is_new);
+ dialog.show_message(_("Loaded OK"));
+ if (get_syntax_auto_detection())
+ detect_syntax();
+ else
+ wedit.set_syn_hlt(EditBox::synhltOff);
+ return true;
+ }
+}
+
+// insert_file() - insert file at the cursor location.
+
+bool Editor::insert_file(const char *filename, const char *specified_encoding)
+{
+ bool dummy_is_new;
+ u8string dummy_effective_encoding;
+ status.invalidate_view();
+
+ if (!xload_file(&wedit, filename, specified_encoding,
+ get_default_encoding(), dummy_effective_encoding,
+ dummy_is_new, false)) {
+ show_file_io_error(_("Loading %s failed: %s"), filename);
+ return false;
+ } else {
+ dialog.show_message(_("Inserted OK"));
+ return true;
+ }
+}
+
+INTERACTIVE void Editor::save_file_as()
+{
+ u8string qry_filename = get_filename(), qry_encoding;
+ if (query_filename(_("Save file as:"), qry_filename, qry_encoding, SAVEAS_HISTORY))
+ save_file(qry_filename.c_str(),
+ qry_encoding.empty() ? get_encoding() : qry_encoding.c_str());
+}
+
+INTERACTIVE void Editor::save_file()
+{
+ if (is_untitled())
+ save_file_as();
+ else
+ save_file(get_filename(), get_encoding());
+}
+
+// emergency_save() - called by the SIGHUP handler to save the buffer.
+
+void Editor::emergency_save()
+{
+ if (wedit.is_modified()) {
+ u8string filename;
+ filename.cformat("%s.save", is_untitled() ? PACKAGE : get_filename());
+ save_file(filename.c_str(), get_encoding());
+ }
+}
+
+INTERACTIVE void Editor::load_file()
+{
+ if (wedit.is_modified())
+ if (!dialog.ask_yes_or_no(_("Buffer was modified; discard changes?")))
+ return;
+ u8string qry_filename, qry_encoding;
+ if (query_filename(_("Open file:"), qry_filename, qry_encoding, LOAD_HISTORY))
+ load_file(qry_filename.c_str(),
+ qry_encoding.empty() ? NULL : qry_encoding.c_str());
+}
+
+INTERACTIVE void Editor::insert_file()
+{
+ u8string qry_filename, qry_encoding;
+ if (query_filename(_("Insert file:"), qry_filename, qry_encoding, INSERT_HISTORY))
+ insert_file(qry_filename.c_str(),
+ qry_encoding.empty() ? NULL : qry_encoding.c_str());
+}
+
+INTERACTIVE void Editor::change_directory()
+{
+ u8string qry_dirname, dummy;
+ if (query_filename(_("Change directory to:"), qry_dirname, dummy,
+ CHDIR_HISTORY, InputLine::cmpltDirectories)) {
+ if (chdir(qry_dirname.c_str()) == -1) {
+ set_last_error(errno);
+ show_file_io_error(_("Can't chdir to %s: %s"), qry_dirname.c_str());
+ }
+ }
+}
+
+void Editor::set_theme(const char *theme)
+{
+ ThemeError theme_error;
+ bool rslt;
+ if (!theme)
+ rslt = load_default_theme(theme_error);
+ else
+ rslt = load_theme(theme, theme_error);
+ if (!rslt)
+ show_file_io_error(_("Error: %s"), theme_error.format().c_str());
+ refresh();
+}
+
+void Editor::set_default_theme()
+{
+ set_theme(NULL);
+}
+
+const char *Editor::get_theme_name()
+{
+ return ::get_theme_name();
+}
+
+void Editor::menu_set_encoding(bool default_encoding)
+{
+ unistring answer = dialog.query(
+ _("Enter encoding name (do 'iconv -l' at the shell for a list):"));
+ if (!answer.empty()) {
+ if (default_encoding)
+ set_default_encoding(u8string(answer).c_str());
+ else
+ set_encoding(u8string(answer).c_str());
+ }
+}
+
+// extract_encoding() - takes the filename that the user has entered,
+// of the form "/path/to/filename -encoding", and returns the "encoding"
+// part. it also removes it from the filename.
+
+static u8string extract_encoding(u8string &filename)
+{
+ u8string encoding;
+ int pos = filename.rindex(' ');
+ if (pos != -1 && (filename[pos + 1] == '-' || filename[pos + 1] == '+')) {
+ encoding.assign(filename, pos + 2, filename.size() - pos - 2);
+ // we've found it. now remove the encoding part from the filename itself
+ while (pos >= 0 && filename[pos] == ' ')
+ --pos;
+ filename.erase(filename.begin() + pos + 1, filename.end());
+ }
+ return encoding;
+}
+
+// query_filename() - prompt the user for a filename.
+
+bool Editor::query_filename(const char *label, u8string &qry_filename,
+ u8string &qry_encoding, int history_set,
+ InputLine::CompleteType complete)
+{
+ unistring uni_filename;
+ uni_filename.init_from_filename(qry_filename.c_str());
+ // we feed dialog.query() not qry_filename but uni_filename.
+ // this extra conversion is needed because qry_filename may not be
+ // a valid UTF-8 string (this is possible since using UTF-8 for
+ // filenames is only a convention).
+ unistring answer = dialog.query(label, uni_filename, history_set, complete);
+ qry_filename.init_from_unichars(answer);
+ // extract encoding, but only if it's not a pipe.
+ if (!qry_filename.empty() && qry_filename[0] != '|' && qry_filename[0] != '!')
+ qry_encoding = extract_encoding(qry_filename);
+ else
+ qry_encoding = "";
+ if (qry_filename == "~")
+ qry_filename = "~/";
+ expand_tilde(qry_filename);
+ return !qry_filename.empty();
+}
+
+INTERACTIVE void Editor::change_tab_width()
+{
+ int num = dialog.get_number(_("Enter tab width:"), wedit.get_tab_width());
+ if (num <= 80) // arbitrary sanity check
+ set_tab_width(num);
+}
+
+INTERACTIVE void Editor::change_justification_column()
+{
+ int num = dialog.get_number(_("Enter justification column:"),
+ wedit.get_justification_column());
+ set_justification_column(num);
+}
+
+INTERACTIVE void Editor::change_scroll_step()
+{
+ int num = dialog.get_number(_("Enter scroll step:"), wedit.get_scroll_step());
+ wedit.set_scroll_step(num);
+}
+
+INTERACTIVE void Editor::insert_unicode_char()
+{
+ unistring answer = dialog.query(
+ _("Enter unicode hex value (or end in '.' for base 10):"));
+ if (answer.empty())
+ return;
+ u8string numstr;
+ numstr.init_from_unichars(answer);
+ errno = 0;
+ char *endp;
+ unichar ch = strtol(numstr.c_str(), &endp,
+ strchr(numstr.c_str(), '.') ? 10 : 16);
+ if (!errno && (*endp == '.' || *endp == '\0'))
+ wedit.insert_char(ch);
+}
+
+void Editor::log2vis(const char *options)
+{
+ wedit.log2vis(options);
+}
+
+INTERACTIVE void Editor::toggle_arabic_shaping()
+{
+ terminal::do_arabic_shaping = !terminal::do_arabic_shaping;
+ wedit.reformat();
+ if (terminal::is_interactive())
+ refresh();
+}
+
+INTERACTIVE void Editor::toggle_graphical_boxes()
+{
+ terminal::graphical_boxes = !terminal::graphical_boxes;
+ if (terminal::is_interactive())
+ refresh();
+}
+
+void Editor::detect_syntax()
+{
+#define NMHAS(EXT) (strstr(get_filename(), EXT))
+ if (NMHAS(".htm") || NMHAS(".HTM"))
+ wedit.set_syn_hlt(EditBox::synhltHTML);
+ else if (NMHAS(".letter") || NMHAS(".article") || NMHAS(".followup") ||
+ NMHAS(".eml") || NMHAS("SLRN") || NMHAS("pico") || NMHAS("mutt"))
+ // the above strings were taken from Vim's cfg.
+ wedit.set_syn_hlt(EditBox::synhltEmail);
+ else
+ wedit.detect_syntax();
+#undef NMHAS
+}
+
+void Editor::set_syntax_auto_detection(bool v)
+{
+ syntax_auto_detection = v;
+ if (v)
+ detect_syntax();
+}
+
+INTERACTIVE void Editor::toggle_syntax_auto_detection()
+{
+ set_syntax_auto_detection(!get_syntax_auto_detection());
+}
+
+INTERACTIVE void Editor::go_to_line()
+{
+ bool canceled;
+ int num = dialog.get_number(_("Go to line #:"), 0, &canceled);
+ if (!canceled)
+ go_to_line(num);
+}
+
+void Editor::search_forward(const unistring &search)
+{
+ if (!wedit.search_forward(search))
+ show_kbd_error(_("Not found"));
+ last_searched_string = search;
+}
+
+INTERACTIVE void Editor::search_forward()
+{
+ bool alt_kbd = wedit.get_alt_kbd();
+ unistring search = dialog.query(_("Search forward:"),
+ last_searched_string, SEARCH_HISTORY,
+ InputLine::cmpltOff, &alt_kbd);
+ wedit.set_alt_kbd(alt_kbd);
+ if (!search.empty())
+ search_forward(search);
+}
+
+INTERACTIVE void Editor::search_forward_next()
+{
+ if (!last_searched_string.empty())
+ search_forward(last_searched_string);
+ else
+ search_forward();
+}
+
+u8string Editor::get_external_editor()
+{
+ if (!external_editor.empty())
+ return external_editor;
+
+ // User hasn't specified an editor, so we figure one out ourselves:
+ // First try $EDITOR, them gvim, then vim, then ...
+ if (getenv("EDITOR") && *getenv("EDITOR"))
+ return getenv("EDITOR");
+ if (terminal::under_x11() && has_prog("gvim"))
+ return "gvim -f"; // `-f' = not to fork and detach
+ if (has_prog("vim"))
+ return "vim";
+ if (has_prog("emacs"))
+ return "emacs";
+ if (has_prog("pico"))
+ return "pico";
+ return "vi";
+}
+
+void Editor::set_external_editor(const char *cmd)
+{
+ external_editor = cmd;
+}
+
+INTERACTIVE void Editor::external_edit_prompt()
+{
+ external_edit(true);
+}
+
+INTERACTIVE void Editor::external_edit_no_prompt()
+{
+ external_edit(false);
+}
+
+// external_edit() launches an external editor to edit the file
+// and then reload it.
+
+void Editor::external_edit(bool prompt)
+{
+ // Step 1: we may first need to save the file.
+
+ if (is_untitled()) {
+ if (dialog.ask_yes_or_no(_("First I must save this buffer as a file; is it OK with you?")))
+ save_file();
+ } else if (wedit.is_modified()) {
+ if (dialog.ask_yes_or_no(_("Buffer was modified and I must save it first; is it OK with you?")))
+ save_file();
+ }
+ if (is_untitled() || wedit.is_modified())
+ return;
+
+ // Step 2: get the editor command
+
+ cstring command = get_external_editor();
+ if (prompt) {
+ unistring inpt = dialog.query(_("External editor:"),
+ command.c_str(),
+ EXTERNALEDITOR_HISTORY,
+ InputLine::cmpltAll);
+ command = u8string(inpt).trim();
+ if (command.empty()) // user aborted
+ return;
+ set_external_editor(command.c_str());
+ }
+
+ // Step 3: write the cursor location into a temporary file,
+ // pointed to by $GERESH_CURSOR_FILE
+
+ cstring cursor_file;
+ //cursor_file = tmpnam(NULL); // gives linker warning!
+ cursor_file.cformat("%s/geresh-%ld-cursor.tmp",
+ getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp",
+ (long)getpid());
+
+ Point cursor;
+ wedit.get_cursor_position(cursor);
+
+ FILE *cursor_fh;
+ if ((cursor_fh = fopen(cursor_file.c_str(), "w")) != NULL) {
+ fprintf(cursor_fh, "%d %d\n%s\n",
+ cursor.para + 1, cursor.pos + 1, get_filename());
+ fclose(cursor_fh);
+
+ cstring nameval;
+ nameval.cformat("GERESH_CURSOR_FILE=%s", cursor_file.c_str());
+ putenv(strdup(nameval.c_str()));
+ }
+
+ // Step 4: adjust the editor command so the line number is passed.
+
+ if (command.index("vim") != -1) {
+ // With vim we not only pass the column number in addition
+ // to the line number, but we also pass the necessary
+ // command to write the cursor position back into
+ // $GERESH_CURSOR_FILE.
+ cstring basefilename = get_filename();
+ if (basefilename.index('/') != -1)
+ basefilename = basefilename.substr(basefilename.rindex('/')+1);
+ cstring col_command;
+ if (cursor.pos != 0)
+ col_command.cformat("-c \"normal %dl\"", cursor.pos);
+ command.cformat("%s -c %d -c \"normal 0\" %s "
+ "-c \"au BufUnload %s call system('echo '.line('.').' '.col('.').' >%s')\"",
+ command.c_str(),
+ cursor.para + 1,
+ col_command.c_str(),
+ basefilename.c_str(),
+ cursor_file.c_str());
+ }
+ else if (command.index("vi") != -1 ||
+ command.index("pico") != -1 ||
+ command.index("nano") != -1 ||
+ command.index("emacs") != -1 ||
+ command.index("mined") != -1)
+ {
+ // The above editors are known to accept a "+line" parameter.
+ command.cformat("%s +%d", command.c_str(), cursor.para + 1);
+ }
+
+ command += " \"";
+ command += get_filename();
+ command += "\"";
+
+ // Step 5: execute the editor.
+
+ cstring msg;
+ msg.cformat("Executing: %s", command.c_str());
+ dialog.show_message(msg.c_str());
+ update_terminal();
+
+ endwin();
+ int status = system(command.c_str());
+ doupdate();
+
+ // Step 6: reload the file.
+
+ // I can't just pass 'get_filename()' to load_file(), because
+ // get_filename() returns a pointer to a string which load_file()
+ // modifies (via set_filename()) ...
+ load_file(u8string(get_filename()).c_str(),
+ u8string(get_encoding()).c_str());
+
+ // Step 7: read the cursor position from $GERESH_CURSOR_FILE.
+
+ if ((cursor_fh = fopen(cursor_file.c_str(), "r")) != NULL) {
+ int line_no, col_no;
+ fscanf(cursor_fh, "%d %d", &line_no, &col_no);
+ cursor.para = line_no - 1;
+ cursor.pos = col_no - 1;
+ wedit.set_cursor_position(cursor);
+ fclose(cursor_fh);
+ } else {
+ wedit.set_cursor_position(cursor);
+ }
+ unlink(cursor_file.c_str());
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
+ msg.cformat(_("Error: command \"%s\" could not be found"),
+ get_external_editor().c_str());
+ dialog.show_message(msg.c_str());
+ }
+
+ refresh_and_center();
+}
+
+// layout_windows() - is called every time the window changes its size.
+
+INTERACTIVE void Editor::layout_windows()
+{
+ int cols, rows;
+ getmaxyx(stdscr, rows, cols);
+
+ int speller_height = 0;
+
+ if (spellerwnd) {
+ // determine the height of the speller window
+ // based on the screen size.
+ if (rows <= 10)
+ speller_height = 2;
+ else if (rows <= 16)
+ speller_height = 4;
+ else if (rows <= 20)
+ speller_height = 6;
+ else if (rows <= 26)
+ speller_height = 8;
+ else if (rows <= 36)
+ speller_height = 11;
+ else
+ speller_height = 13;
+
+ spellerwnd->resize(speller_height, cols, rows - 2 - speller_height, 0);
+ }
+
+ if (menubar)
+ menubar->resize(1, cols, 0, 0);
+#define MENUHEIGHT (menubar ? 1 : 0)
+
+ if (scrollbar)
+ scrollbar->resize(rows - 2 - MENUHEIGHT - speller_height, 1,
+ MENUHEIGHT, (scrollbar_pos == scrlbrLeft) ? 0 : cols - 1);
+#define SCROLLBARWIDTH (scrollbar ? 1 : 0)
+
+ wedit.resize(rows - 2 - MENUHEIGHT - speller_height,
+ cols - SCROLLBARWIDTH, MENUHEIGHT,
+ (scrollbar_pos == scrlbrLeft) ? 1 : 0);
+ status.resize(1, cols, rows - 2, 0);
+ dialog.resize(1, cols, rows - 1, 0);
+
+ update_terminal();
+}
+
+// update_terminal() - updates the screen.
+
+void Editor::update_terminal(bool soft)
+{
+ // for every widget that's dirty, call its update() method to update
+ // stdscr. If any was dirty, call doupdate() to update the physical
+ // screen.
+
+ bool need_dorefresh = false;
+
+ if (scrollbar && scrollbar->is_dirty()) {
+ scrollbar->update();
+ need_dorefresh = true;
+ }
+
+ if (menubar && menubar->is_dirty()) {
+ menubar->update();
+ need_dorefresh = true;
+ }
+
+ if (status.is_dirty()) {
+ status.update();
+ need_dorefresh = true;
+ }
+
+ if (dialog.is_dirty()) {
+ dialog.update();
+ need_dorefresh = true;
+ }
+
+ if (wedit.is_dirty()) {
+ wedit.update();
+ need_dorefresh = true;
+ } else if (need_dorefresh) {
+ // make sure wedit gets the cursor even if some
+ // of the other widgets get drawn.
+ wedit.update_cursor();
+ }
+
+ if (spellerwnd) {
+ if (spellerwnd->is_dirty()) {
+ spellerwnd->update();
+ need_dorefresh = true;
+ } else if (need_dorefresh) {
+ // spellerwnd always gets the cursor.
+ spellerwnd->update_cursor();
+ }
+ }
+
+ if (!soft && need_dorefresh)
+ doupdate();
+}
+
+// Editor::exec() - the main event loop. it reads events and either handles
+// them or send them on to the editbox.
+
+void Editor::exec()
+{
+ finished = false;
+ while (!finished) {
+ Event evt;
+ update_terminal();
+ get_next_event(evt, wedit.wnd);
+ dialog.clear_transient_message();
+ if (!evt.is_literal())
+ if (handle_event(evt))
+ continue;
+ wedit.handle_event(evt);
+ if (scrollbar)
+ wedit.sync_scrollbar(scrollbar);
+ }
+}
+
+// load_unload_speller() - interactively loads and unloads a speller
+// process. When the user interactively loads a speller, he is asked to
+// specify the speller-command and the speller-encoding.
+
+INTERACTIVE void Editor::load_unload_speller()
+{
+ if (speller.is_loaded()) {
+ speller.unload();
+ } else {
+ // We're about to load the speller. query the user for cmd / encoding.
+ unistring cmd = dialog.query(_("Enter speller command:"),
+ get_speller_cmd(), SPELER_CMD_HISTORY, InputLine::cmpltAll);
+ if (cmd.empty())
+ return;
+ if (cmd.index(u8string("-a")) == -1) {
+ if (!dialog.ask_yes_or_no(
+ _("There's no `-a' option in the command, proceed anyway?")))
+ return;
+ }
+ unistring encoding = dialog.query(_("Enter speller encoding:"),
+ get_speller_encoding(), SPELER_ENCODING_HISTORY);
+ if (encoding.empty())
+ return;
+ set_speller_cmd(u8string(cmd).c_str());
+ set_speller_encoding(u8string(encoding).c_str());
+ speller.load(get_speller_cmd(), get_speller_encoding());
+ }
+ status.invalidate_view();
+}
+
+// adjust_speller_cmd() - if the user hasn't specified a speller command,
+// figure it out ourselves.
+// Prefer: multispell, then ispell, and finally aspell.
+// Use the ISO-8859-8 encoding for Hebrew spell-checkers.
+
+void Editor::adjust_speller_cmd()
+{
+ bool no_cmd = (*get_speller_cmd() == '\0');
+ bool no_encoding = (*get_speller_encoding() == '\0');
+
+ if (no_cmd) {
+ bool has_multispell = has_prog("multispell");
+ bool has_ispell = has_prog("ispell");
+ bool has_aspell = !has_ispell && has_prog("aspell"); // optimization: search aspell
+ // only if no ispell found.
+ if (has_multispell) {
+ u8string cmd;
+ if (has_ispell || has_aspell) {
+ cmd = "LC_ALL=C multispell -a -n";
+ if (!has_ispell && has_aspell)
+ cmd += " --ispell-cmd=aspell";
+ } else {
+ cmd = "LC_ALL=C hspell -a -n";
+ }
+ set_speller_cmd(cmd.c_str());
+ set_speller_encoding("ISO-8859-8");
+ } else {
+ if (!has_ispell && has_aspell)
+ set_speller_cmd("aspell -a");
+ else
+ set_speller_cmd("ispell -a");
+ if (no_encoding)
+ set_speller_encoding("ISO-8859-1");
+ }
+ } else if (no_encoding) {
+ if (strstr(get_speller_cmd(), "hspell") || strstr(get_speller_cmd(), "multispell"))
+ set_speller_encoding("ISO-8859-8");
+ else
+ set_speller_encoding("ISO-8859-1");
+ }
+}
+
+INTERACTIVE void Editor::spell_check_all()
+{
+ spell_check(Speller::splRngAll);
+}
+
+INTERACTIVE void Editor::spell_check_forward()
+{
+ spell_check(Speller::splRngForward);
+}
+
+INTERACTIVE void Editor::spell_check_word()
+{
+ spell_check(Speller::splRngWord);
+}
+
+void Editor::spell_check(Speller::splRng range)
+{
+ if (!spellerwnd) {
+ spellerwnd = new SpellerWnd(*this);
+ layout_windows();
+ }
+
+ // if the speller is not yet loaded, load it with the default
+ // parameters.
+ if (!speller.is_loaded()) {
+ speller.load(get_speller_cmd(), get_speller_encoding());
+ status.invalidate_view();
+ update_terminal(); // immediately update the status line
+ }
+
+ if (speller.is_loaded())
+ speller.spell_check(range, wedit, *spellerwnd);
+
+ // The speller window exists during the spell check only. we
+ // delete it afterwards.
+ delete spellerwnd;
+ spellerwnd = NULL;
+ layout_windows();
+}
+
+// show_hint() is used to print the popdown menu hints. Screen is updated
+// only after doupdate()
+
+void Editor::show_hint(const char *msg)
+{
+ dialog.show_message(msg);
+ dialog.update();
+}
+
+// show_kbd_error() - prints a transient error message and rings the bell.
+// it is usually used for "keyboard" errors and the bell is supposed to
+// catch the users attention.
+
+void Editor::show_kbd_error(const char *msg)
+{
+ // we don't use show_error_message() because we want the
+ // message to disappear at the next event.
+ dialog.show_message(msg);
+ Widget::signal_error();
+}
+
+void Editor::on_read_only_error(unichar ch)
+{
+ unistring quit_chars;
+ // when the buffer is in read-only mode, we let the user exit
+ // by pressing 'q'.
+ quit_chars.init_from_utf8(_("qQ"));
+ if (quit_chars.has_char(ch))
+ quit();
+ else
+ show_kbd_error(_("Buffer is read-only"));
+}
+
+void Editor::on_no_selection_error()
+{
+ show_kbd_error(_("No text is selected"));
+}
+
+void Editor::on_no_alt_kbd_error()
+{
+ show_kbd_error(_("No alternate keyboard (Hebrew) was loaded"));
+}
+
+void Editor::on_no_translation_table_error()
+{
+ show_kbd_error(_("No translation table was loaded"));
+}
+
+void Editor::on_cant_display_nsm_error()
+{
+ show_kbd_error(_("Terminal can't display non-spacing marks (like Hebrew points)"));
+}
+
diff --git a/editor.h b/editor.h
new file mode 100644
index 0000000..fe8d535
--- /dev/null
+++ b/editor.h
@@ -0,0 +1,232 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_EDITOR_H
+#define BDE_EDITOR_H
+
+#include "editbox.h"
+#include "dialogline.h"
+#include "statusline.h"
+#include "inputline.h"
+#include "speller.h"
+
+class Menubar;
+class Scrollbar;
+
+class Editor : public Dispatcher, public EditBoxErrorListener {
+public:
+ enum scrollbar_pos_t { scrlbrNone, scrlbrLeft, scrlbrRight };
+
+protected:
+
+ EditBox wedit;
+ DialogLine dialog;
+ StatusLine status;
+ Menubar *menubar;
+ Scrollbar *scrollbar;
+ Speller speller;
+ SpellerWnd *spellerwnd;
+
+ u8string filename;
+ u8string encoding;
+ u8string default_encoding;
+ bool new_flag; // is this a new file?
+ u8string backup_suffix;
+
+ u8string speller_encoding;
+ u8string speller_cmd;
+ u8string external_editor;
+
+ unistring last_searched_string; // for the "search next" command.
+
+ bool finished; // exec() quits when this flag is set.
+
+ static Editor *global_instance; // for use by the SIGHUP handler.
+
+#ifdef HAVE_CURS_SET
+ bool big_cursor;
+#endif
+
+ scrollbar_pos_t scrollbar_pos;
+ bool syntax_auto_detection;
+
+ //
+
+ bool query_filename(const char *label, u8string &qry_filename,
+ u8string &qry_encoding, int history_set = 0,
+ InputLine::CompleteType complete = InputLine::cmpltAll);
+ void show_kbd_error(const char *msg);
+
+public:
+
+ HAS_ACTIONS_MAP(Editor, Dispatcher);
+ HAS_BINDINGS_MAP(Editor, Dispatcher);
+
+ Editor();
+ virtual ~Editor() {}
+
+ static Editor *get_global_instance() {
+ return global_instance;
+ }
+
+ //
+ // Setters / Getters
+ //
+ const char *get_encoding() const { return encoding.c_str(); }
+ const char *get_default_encoding() const { return default_encoding.c_str(); }
+ const char *get_filename() const { return filename.c_str(); }
+ const char *get_backup_suffix() const { return backup_suffix.c_str(); }
+ const char *get_speller_encoding() const { return speller_encoding.c_str(); }
+ const char *get_speller_cmd() const { return speller_cmd.c_str(); }
+ void set_encoding(const char *s);
+ void set_default_encoding(const char *s) { default_encoding = u8string(s); }
+ void set_filename(const char *s) { filename = u8string(s); }
+ void set_backup_suffix(const char *s) { backup_suffix = u8string(s); }
+ void set_speller_encoding(const char *s) { speller_encoding = u8string(s); }
+ void set_speller_cmd(const char *s) { speller_cmd = u8string(s); }
+ u8string get_external_editor();
+ void set_external_editor(const char *cmd);
+ bool is_new() const { return new_flag; }
+ bool is_untitled() const { return filename.empty(); }
+ void set_new(bool value) { new_flag = value; }
+ bool is_speller_loaded() const { return speller.is_loaded(); }
+ bool in_spelling() const { return spellerwnd != NULL; }
+ void set_scrollbar_pos(scrollbar_pos_t);
+ scrollbar_pos_t get_scrollbar_pos() const { return scrollbar_pos; }
+ void adjust_speller_cmd();
+ void set_theme(const char *theme);
+ INTERACTIVE void set_default_theme();
+ const char *get_theme_name();
+
+ //
+ // Methods to access EditBox. These are used to set
+ // command-line options.
+ //
+ void set_tab_width(int width) { wedit.set_tab_width(width); }
+ void set_justification_column(int column)
+ { wedit.set_justification_column(column); }
+ void toggle_auto_indent() { wedit.toggle_auto_indent(); }
+ void toggle_auto_justify() { wedit.toggle_auto_justify(); }
+ void toggle_formatting_marks() { wedit.toggle_formatting_marks(); }
+ void go_to_line(int line) { wedit.move_absolute_line(line - 1); }
+ void set_wrap_type(EditBox::WrapType value) { wedit.set_wrap_type(value); }
+ void set_dir_algo(diralgo_t value) { wedit.set_dir_algo(value); }
+ void set_scroll_step(int value) { wedit.set_scroll_step(value); }
+ void set_smart_typing(bool value) { wedit.set_smart_typing(value); }
+ void set_rfc2646_trailing_space(bool value)
+ { wedit.set_rfc2646_trailing_space(value); }
+ void set_maqaf_display(EditBox::maqaf_display_t disp)
+ { wedit.set_maqaf_display(disp); }
+ void set_rtl_nsm_display(EditBox::rtl_nsm_display_t disp)
+ { wedit.set_rtl_nsm_display(disp); }
+ void set_undo_size_limit(size_t limit)
+ { wedit.set_undo_size_limit(limit); }
+ void set_key_for_key_undo(bool value)
+ { wedit.set_key_for_key_undo(value); }
+ void set_read_only(bool value)
+ { wedit.set_read_only(value); }
+ void set_non_interactive_text_width(int cols)
+ { wedit.set_non_interactive_text_width(cols); }
+ void enable_bidi(bool value) { wedit.enable_bidi(value); }
+ void log2vis(const char *options);
+ bool get_syntax_auto_detection()
+ { return syntax_auto_detection; }
+ void set_syntax_auto_detection(bool value);
+ void set_underline(bool value) { wedit.set_underline(value); }
+ void set_visual_cursor_movement(bool value)
+ { wedit.set_visual_cursor_movement(value); }
+
+ //
+ // Interactive commands
+ //
+ INTERACTIVE void show_character_info();
+ INTERACTIVE void show_character_code();
+ INTERACTIVE void refresh_and_center();
+ INTERACTIVE void describe_key();
+ INTERACTIVE void toggle_cursor_position_report();
+ bool is_cursor_position_report() const;
+ INTERACTIVE void quit();
+ INTERACTIVE void layout_windows();
+ INTERACTIVE void menu();
+ INTERACTIVE void help();
+ void show_help_topic(const char *topic);
+ INTERACTIVE void save_file();
+ INTERACTIVE void save_file_as();
+ INTERACTIVE void load_file();
+ INTERACTIVE void insert_file();
+ INTERACTIVE void write_selection_to_file();
+ INTERACTIVE void change_tab_width();
+ INTERACTIVE void change_justification_column();
+ INTERACTIVE void insert_unicode_char();
+ INTERACTIVE void go_to_line();
+ INTERACTIVE void search_forward();
+ INTERACTIVE void search_forward_next();
+ INTERACTIVE void change_directory();
+ INTERACTIVE void toggle_arabic_shaping();
+ INTERACTIVE void toggle_graphical_boxes();
+ INTERACTIVE void change_scroll_step();
+ void menu_set_encoding(bool default_encoding);
+ INTERACTIVE void menu_set_scrollbar_none();
+ INTERACTIVE void menu_set_scrollbar_left();
+ INTERACTIVE void menu_set_scrollbar_right();
+ INTERACTIVE void load_unload_speller();
+ INTERACTIVE void spell_check_all();
+ INTERACTIVE void spell_check_forward();
+ INTERACTIVE void spell_check_word();
+ void spell_check(Speller::splRng range);
+#ifdef HAVE_CURS_SET
+ INTERACTIVE void toggle_big_cursor();
+#endif
+ INTERACTIVE void toggle_syntax_auto_detection();
+ INTERACTIVE void external_edit_prompt();
+ INTERACTIVE void external_edit_no_prompt();
+ void external_edit(bool prompt);
+
+ //
+ // Misc
+ //
+ void exec();
+ void emergency_save();
+ void show_file_io_error(const char *msg, const char *filename);
+ bool load_file(const char *filename, const char *specified_encoding);
+ bool save_file(const char *filename, const char *specified_encoding);
+ bool write_selection_to_file(const char *filename,
+ const char *specified_encoding);
+ bool insert_file(const char *raw_filename, const char *encoding);
+ void search_forward(const unistring &search);
+ void refresh(bool soft = false);
+ void update_terminal(bool soft = false);
+ void show_hint(const char *msg);
+ void detect_syntax();
+
+#ifdef HAVE_CURS_SET
+ bool is_big_cursor() const { return big_cursor; }
+ void set_big_cursor(bool value) {
+ big_cursor = value;
+ curs_set(value ? 2 : 1);
+ }
+#endif
+
+ // EditBoxErrorListener implementation
+ virtual void on_read_only_error(unichar ch);
+ virtual void on_no_selection_error();
+ virtual void on_no_alt_kbd_error();
+ virtual void on_no_translation_table_error();
+ virtual void on_cant_display_nsm_error();
+};
+
+#endif
+
diff --git a/event.cc b/event.cc
new file mode 100644
index 0000000..bef0cfc
--- /dev/null
+++ b/event.cc
@@ -0,0 +1,189 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "event.h"
+#include "my_wctob.h"
+#include "dbg.h"
+
+static const char *ext_keyname(int keycode)
+{
+ switch (keycode) {
+ case KEY_DOWN: return "Down";
+ case KEY_UP: return "Up";
+ case KEY_LEFT: return "Left";
+ case KEY_RIGHT: return "Right";
+ case KEY_HOME: return "Home";
+ case KEY_END: return "End";
+ case KEY_BACKSPACE: return "BkSp";
+ case KEY_DC: return "Del";
+ case KEY_IC: return "Insert";
+ case KEY_NPAGE: return "PgDn";
+ case KEY_PPAGE: return "PgUp";
+ default: return keyname(keycode);
+ }
+}
+
+u8string Event::to_string() const
+{
+ u8string s;
+ if ((modifiers & VIRTUAL) || (keycode == 0 && !ch)) {
+ // No actual event
+ s = "";
+ } else if (is_literal()) {
+ s = u8string(unistring(&ch, &ch + 1));
+ } else {
+ //DBG(2, ("comp: "));
+ if (modifiers) {
+ if (modifiers & CTRL)
+ s += "C-";
+ if (modifiers & ALT)
+ s += "M-";
+ if (modifiers & SHIFT)
+ s += "Shift-";
+ }
+ if (ch) {
+ if ((modifiers & ALT) && ch == '\t') {
+ // Hmmm... this is a special case. Ctrl-I is actually TAB.
+ return u8string("C-M-i");
+ }
+ if ((modifiers & CTRL) && ch == '^') {
+ // Hmmm... this is a more friendly representation.
+ return u8string("C-Spc");
+ }
+ s += u8string(unistring(&ch, &ch + 1)).c_str();
+ } else {
+ if (keycode >= KEY_F(0) && keycode <= KEY_F(63)) {
+ u8string num;
+ num.cformat("F%d", keycode - KEY_F(0));
+ s += num;
+ } else {
+ s += ext_keyname(keycode);
+ }
+ }
+ }
+ return s;
+}
+
+void Event::print()
+{
+ DBG(2, ("EVENT: "));
+ if (is_literal())
+ DBG(2, ("literal: '%lc'\n", ch));
+ else {
+ DBG(2, ("comp: "));
+ if (modifiers) {
+ if (modifiers & CTRL)
+ DBG(2, ("CTRL-"));
+ if (modifiers & ALT)
+ DBG(2, ("ALT-"));
+ if (modifiers & SHIFT)
+ DBG(2, ("SHIFT-"));
+ }
+ if (ch)
+ DBG(2, ("'%lc'\n", ch));
+ else
+ DBG(2, ("%s\n", keyname(keycode)));
+ }
+}
+
+#ifdef HAVE_WIDE_CURSES
+static void low_level_get_wch(Event &evt, WINDOW *wnd)
+{
+ wint_t c;
+ int ret;
+ do {
+ ret = wget_wch(wnd, &c);
+ if (ret == KEY_CODE_YES) {
+ evt.ch = 0;
+ evt.keycode = (int)c;
+ } else if (ret == OK) {
+ evt.keycode = 0;
+ evt.ch = (unichar)c;
+ }
+ } while (ret == ERR);
+}
+#else
+static void low_level_get_wch(Event &evt, WINDOW *wnd)
+{
+ int ret;
+ do {
+ ret = wgetch(wnd);
+ if (ret >= 256) {
+ evt.ch = 0;
+ evt.keycode = ret;
+ } else {
+ evt.keycode = 0;
+ evt.ch = terminal::force_iso88598 ? iso88598_to_unicode(ret) : BTOWC(ret);
+ }
+ } while (ret == ERR);
+}
+#endif
+
+static void get_base_event(Event &evt, WINDOW *wnd)
+{
+ evt.type = evtKbd;
+ evt.modifiers = 0;
+ low_level_get_wch(evt, wnd);
+ if (evt.keycode == 0 && evt.ch < 32) {
+ switch (evt.ch) {
+ case 9:
+ case 13:
+ case 27:
+ // leave alone TAB, ENTER, ESC
+ break;
+ case 0:
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ evt.ch += 'A' - 1;
+ evt.modifiers = CTRL;
+ break;
+ default:
+ evt.ch += 'a' - 1;
+ evt.modifiers = CTRL;
+ }
+ }
+}
+
+bool is_event_pending = false;
+Event pending_event;
+
+void get_next_event(Event &evt, WINDOW *wnd)
+{
+ if (is_event_pending) {
+ evt = pending_event;
+ is_event_pending = false;
+ return;
+ }
+
+ get_base_event(evt, wnd);
+ if (evt.ch == 27) {
+ // emulate ALT
+ get_base_event(evt, wnd);
+ if (evt.ch != 27) // ...but make sure we can still generate ESCape
+ evt.modifiers |= ALT;
+ }
+}
+
+void set_next_event(const Event &evt)
+{
+ is_event_pending = true;
+ pending_event = evt;
+}
+
diff --git a/event.h b/event.h
new file mode 100644
index 0000000..c374bc0
--- /dev/null
+++ b/event.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_EVENT_H
+#define BDE_EVENT_H
+
+#include "types.h"
+#include "terminal.h"
+
+#ifdef CTRL
+# undef CTRL
+#endif
+
+#define SHIFT 1
+#define CTRL 2
+#define ALT 4
+#define VIRTUAL 8
+
+enum EventType { evtKbd, evtMouse };
+
+class Event {
+public:
+
+ EventType type;
+
+ int modifiers;
+ unichar ch;
+ int keycode;
+
+ bool operator== (const Event &other) const
+ {
+ return type == other.type &&
+ modifiers == other.modifiers &&
+ ch == other.ch &&
+ keycode == other.keycode;
+ }
+
+ Event() { }
+
+ Event(int modifiers, unichar ch, int keycode = 0)
+ {
+ type = evtKbd;
+ this->modifiers = modifiers;
+ this->ch = ch;
+ this->keycode = keycode;
+ }
+
+ Event(int keycode)
+ {
+ type = evtKbd;
+ this->modifiers = 0;
+ this->ch = 0;
+ this->keycode = keycode;
+ }
+
+ bool is_literal() const
+ {
+ return (ch != 0 && ch != 27/*ESC*/ && modifiers == 0);
+ }
+
+ bool empty() const
+ {
+ return !modifiers && !ch && !keycode;
+ }
+
+ u8string to_string() const;
+
+ void print();
+};
+
+void get_next_event(Event &evt, WINDOW *wnd);
+void set_next_event(const Event &evt);
+
+#endif
+
diff --git a/helpbox.cc b/helpbox.cc
new file mode 100644
index 0000000..153df1e
--- /dev/null
+++ b/helpbox.cc
@@ -0,0 +1,248 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "helpbox.h"
+#include "editor.h"
+#include "io.h"
+#include "pathnames.h"
+#include "themes.h"
+
+std::vector<HelpBox::Position> HelpBox::positions_stack;
+
+HelpBox::HelpBox(Editor *aApp, const EditBox &settings)
+{
+ app = aApp;
+ statusmsg.set_text(_("F1 or ALT-X to exit help, ENTER to follow link, 'l' to move back, 't' to TOC"));
+ statusmsg.highlight();
+
+ set_undo_size_limit(0);
+ set_read_only(true);
+
+ scroll_step = settings.get_scroll_step();
+ rtl_nsm_display = settings.get_rtl_nsm_display();
+ maqaf_display = settings.get_maqaf_display();
+
+ show_explicits = false;
+ show_paragraph_endings = true;
+ show_tabs = true;
+}
+
+INTERACTIVE void HelpBox::layout_windows()
+{
+ app->layout_windows();
+ int cols, rows;
+ getmaxyx(stdscr, rows, cols);
+ resize(rows - 1, cols, 0, 0);
+ statusmsg.resize(1, cols, rows - 1, 0);
+}
+
+bool HelpBox::load_user_manual()
+{
+ u8string dummy_encoding;
+ bool dummy_new;
+ if (!xload_file(this, get_cfg_filename(USER_MANUAL_FILE),
+ NULL, "UTF-8", dummy_encoding, dummy_new, false)
+ && !xload_file(this, get_cfg_filename(SYSTEM_MANUAL_FILE),
+ NULL, "UTF-8", dummy_encoding, dummy_new, false))
+ {
+ app->show_file_io_error(_("Cannot open the manual file %s: %s"),
+ get_cfg_filename(SYSTEM_MANUAL_FILE));
+ return false;
+ } else {
+ scan_toc();
+ pop_position();
+ return true;
+ }
+}
+
+void HelpBox::push_position()
+{
+ Position pos;
+ pos.top_line = top_line;
+ pos.cursor = cursor;
+ positions_stack.push_back(pos);
+}
+
+INTERACTIVE void HelpBox::pop_position()
+{
+ if (positions_stack.size()) {
+ Position pos = positions_stack.back();
+ positions_stack.pop_back();
+ top_line = pos.top_line;
+ if ((int)paragraphs[top_line.para]->line_breaks.size()
+ <= top_line.inner_line)
+ // window probably resized, so previous
+ // inner_line lost.
+ top_line.inner_line = 0;
+ set_cursor_position(pos.cursor);
+ request_update(rgnAll);
+ }
+}
+
+INTERACTIVE void HelpBox::move_to_toc()
+{
+ push_position();
+ top_line = CombinedLine(toc_first_line, 0);
+ set_cursor_position(Point(toc_first_line, 0));
+ request_update(rgnAll);
+}
+
+INTERACTIVE void HelpBox::refresh_and_center()
+{
+ center_line();
+ invalidate_view();
+ statusmsg.invalidate_view();
+ clearok(curscr, TRUE);
+}
+
+INTERACTIVE void HelpBox::jump_to_topic(const char *topic)
+{
+ push_position();
+ move_beginning_of_buffer();
+ while (search_forward(unistring(u8string(topic))) && cursor.pos != 0)
+ ;
+ top_line = CombinedLine(cursor.para, 0);
+ request_update(rgnAll);
+}
+
+// HelpBox::exec() - reads events and hands them to the help window.
+
+void HelpBox::exec()
+{
+ set_modal();
+ while (is_modal()) {
+ Event evt;
+ statusmsg.update();
+ update();
+ doupdate();
+ get_next_event(evt, wnd);
+ handle_event(evt);
+ }
+ push_position();
+}
+
+// scan_toc() - reads the TOC (table of contents) into toc_items
+
+void HelpBox::scan_toc()
+{
+ toc_items.clear();
+ bool in_toc = false;
+ for (int i = 0; i < parags_count(); i++) {
+ const unistring &str = paragraphs[i]->str;
+ if (!in_toc) {
+ if (str.len() && str[0] == '-') {
+ in_toc = true;
+ toc_first_line = i;
+ }
+ }
+ if (in_toc) {
+ if (str.len()) {
+ // add this item to toc_items
+ unistring item = str;
+ while (item.len() &&
+ (item[0] == ' ' || item[0] == '-'))
+ item.erase_head(1);
+ toc_items.push_back(item);
+ } else {
+ // finished processing TOC
+ toc_last_line = i - 1;
+ break;
+ }
+ }
+ }
+}
+
+// jump_to_topic() - reads the topic on which the cursor stands,
+// then jumps to it. Usually called in reply to the ENTER key.
+
+void HelpBox::jump_to_topic()
+{
+ const unistring &str = curr_para()->str;
+ if (cursor.para >= toc_first_line && cursor.para <= toc_last_line) {
+ unistring topic = str;
+ while (topic.len()
+ && (topic[0] == ' ' || topic[0] == '-'))
+ topic.erase_head(1);
+ jump_to_topic(u8string(topic).c_str());
+ } else if (cursor.pos < str.len()) {
+ // read the topic on which the cursor stands
+ idx_t start = cursor.pos;
+ while (start >= 0 && str[start] != '"')
+ start--;
+ idx_t end = cursor.pos;
+ while (end < str.len() && str[end] != '"')
+ end++;
+ if (str[start] == '"' && str[end] == '"' && (start < end)) {
+ unistring topic = str.substr(start + 1, end - start - 1);
+ jump_to_topic(u8string(topic).c_str());
+ }
+ }
+}
+
+// do_syntax_highlight() - highlight all the topic names in the text.
+
+void HelpBox::do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num)
+{
+ attribute_t links_attr = get_attr(EDIT_LINKS_ATTR);
+ if (para_num >= toc_first_line && para_num <= toc_last_line) {
+ // toc
+ int i = 0;
+ while (str[i] == ' ' || str[i] == '-')
+ i++;
+ while (i < str.len())
+ attributes[i++] = links_attr;
+ } else {
+ // normal text: search for the topic names
+ for (int i = 0; i < (int)toc_items.size(); i++) {
+ unistring topic = toc_items[i];
+ topic.insert(topic.begin(), '"');
+ topic.push_back('"');
+ idx_t pos = 0;
+ while ((pos = str.index(topic, pos + 1)) != -1) {
+ for (idx_t k = pos + 1; k < pos + topic.len() - 1; k++)
+ attributes[k] = links_attr;
+ }
+ }
+ }
+}
+
+bool HelpBox::handle_event(const Event &evt)
+{
+ if (evt.is_literal()) {
+ switch (evt.ch) {
+ case 13:
+ jump_to_topic();
+ return true;
+ case 'l':
+ pop_position();
+ return true;
+ case 't':
+ case 'd':
+ move_to_toc();
+ return true;
+ case 'q':
+ end_modal();
+ return true;
+ }
+ }
+ if (!Dispatcher::handle_event(evt))
+ return EditBox::handle_event(evt);
+ return false;
+}
+
diff --git a/helpbox.h b/helpbox.h
new file mode 100644
index 0000000..0b2037c
--- /dev/null
+++ b/helpbox.h
@@ -0,0 +1,51 @@
+#ifndef BDE_HELPBOX_H
+#define BDE_HELPBOX_H
+
+#include "editbox.h"
+#include "label.h"
+
+class Editor;
+
+class HelpBox : public EditBox {
+
+ Editor *app;
+ Label statusmsg;
+
+ int toc_first_line;
+ int toc_last_line;
+ std::vector<unistring> toc_items;
+
+ struct Position {
+ CombinedLine top_line;
+ Point cursor;
+ };
+ static std::vector<Position> positions_stack;
+
+public:
+
+ HAS_ACTIONS_MAP(HelpBox, EditBox);
+ HAS_BINDINGS_MAP(HelpBox, EditBox);
+
+ HelpBox(Editor *aApp, const EditBox &settings);
+ bool load_user_manual();
+ void jump_to_topic(const char *topic);
+
+ void exec();
+ virtual bool handle_event(const Event &evt);
+
+ INTERACTIVE void layout_windows();
+ INTERACTIVE void refresh_and_center();
+ void push_position();
+ INTERACTIVE void pop_position();
+ INTERACTIVE void move_to_toc();
+ INTERACTIVE void jump_to_topic();
+
+protected:
+
+ void scan_toc();
+ virtual void do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num);
+};
+
+#endif
+
diff --git a/inputline.cc b/inputline.cc
new file mode 100644
index 0000000..b09dae9
--- /dev/null
+++ b/inputline.cc
@@ -0,0 +1,443 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#if HAVE_DIRENT_H
+# include <dirent.h> // for filename completion
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <map>
+
+#include "inputline.h"
+#include "io.h" // expand_tilde
+#include "themes.h"
+#include "dbg.h"
+
+// A history is stored as a StringArray. There are usually several histories
+// in one application (for example, the history for an "Open file:" stores
+// file names, and the history for a "Find:" stores search strings). The
+// histories are stored in a history map, and an identifier, history_set, that
+// is passed to InputLine's constructor, is used in selecting the appropriate
+// history.
+
+static std::map<int, InputLine::StringArray> histories;
+
+// init_history() - initializes the "history" pointer and pushes an initial
+// value into it. when history_set is zero, history is disabled for this
+// InputLine object.
+
+void InputLine::init_history(int history_set)
+{
+ if (history_set == 0) {
+ history = NULL;
+ } else {
+ history = &histories[history_set];
+ history_idx = 0;
+
+ // Add the current text to history, provided it's not already
+ // there; if the last entry is the empty string, overwrite
+ // it instead of preserving it.
+ if (history->empty()) {
+ history->insert(history->begin(), get_text());
+ } else {
+ if (history->front().empty())
+ history->front() = get_text();
+ else if (get_text() != history->front())
+ history->insert(history->begin(), get_text());
+ }
+ }
+}
+
+InputLine::InputLine(const char *aLabel, const unistring &default_text,
+ int history_set, CompleteType complete)
+{
+ set_label(aLabel);
+ set_text(default_text);
+ init_history(history_set);
+ complete_type = complete;
+ wrap_type = wrpOff;
+ event_num = 0;
+ last_tab_event_num = -2;
+ set_modal();
+}
+
+void InputLine::set_label(const char *aLabel)
+{
+ unistring label;
+ label.init_from_utf8(aLabel);
+ label_dir = BiDi::determine_base_dir(label.begin(), label.size(), algoUnicode);
+ BiDi::simple_log2vis(label, label_dir, vis_label);
+ label_width = get_str_width(vis_label.begin(), vis_label.size());
+ margin_before = 2 + label_width;
+ margin_after = 2;
+}
+
+void InputLine::set_text(const unistring &s)
+{
+ new_document();
+ insert_text(s, true);
+}
+
+// redraw_paragraph() - override EditBox's method. it calls base and then
+// draws the label.
+
+void InputLine::redraw_paragraph(Paragraph &p,
+ int window_start_line, bool only_cursor, int para_num)
+{
+ // If the label is RTL, it's printed on the right, else --
+ // on the left. We use either margin_before or margin_after to
+ // reserve the space for it.
+
+ if ( (p.is_rtl() && label_dir == dirRTL)
+ || (!p.is_rtl() && label_dir == dirLTR) ) {
+ margin_before = 2 + label_width;
+ margin_after = 1;
+ } else {
+ margin_after = 2 + label_width;
+ margin_before = 1;
+ }
+
+ // Curses bug: don't paint on the bottom-right cell, because
+ // some Curses implementations get confused.
+ if (label_dir == dirLTR && !p.is_rtl()) {
+ margin_after = 2;
+ }
+
+ EditBox::redraw_paragraph(p, window_start_line, only_cursor, para_num);
+
+ if (!only_cursor) {
+ // draw label
+ if (label_dir == dirRTL) {
+ wmove(wnd, 0, window_width()
+ - (p.is_rtl() ? margin_before : margin_after)
+ + 1);
+ draw_unistr(vis_label.begin(), vis_label.size());
+ } else {
+ wmove(wnd, 0, 1);
+ draw_unistr(vis_label.begin(), vis_label.size());
+ }
+ // reposition the cursor
+ EditBox::redraw_paragraph(p, window_start_line, true, para_num);
+ }
+}
+
+// get_directory_files() - populates files_list.
+//
+// @param directory - the directory to list.
+
+void InputLine::get_directory_files(u8string directory, const char *prefix)
+{
+ expand_tilde(directory);
+ files_list.clear();
+
+ DIR *dir = opendir(directory.c_str());
+ if (dir == NULL) {
+ signal_error();
+ return;
+ }
+
+ dirent *ent;
+ u8string pathname = directory;
+ int path_file_pos = pathname.size();
+
+ while ((ent = readdir(dir))) {
+ const char *d_name = ent->d_name;
+ if (STREQ(d_name, ".") || STREQ(d_name, ".."))
+ continue;
+ if (prefix && (strncmp(prefix, d_name, strlen(prefix)) != 0))
+ continue;
+ pathname.erase(path_file_pos, pathname.size());
+ pathname += d_name;
+
+ struct stat file_info;
+ stat(pathname.c_str(), &file_info);
+ bool is_dir = S_ISDIR(file_info.st_mode);
+
+ unistring filename;
+ filename.init_from_filename(d_name);
+ if (is_dir)
+ filename.push_back('/');
+
+ if (complete_type == cmpltAll
+ || (complete_type == cmpltDirectories && is_dir))
+ files_list.push_back(filename);
+ }
+ closedir(dir);
+
+ // it's important that this list be sorted.
+ std::sort(files_list.begin(), files_list.end());
+}
+
+// init_completion() - initialize filename completion. it reads the directory
+// and (partial) filename components under the cursor, calls
+// get_directory_files() to list the files, and sets slice_begin and
+// slice_end to the relevant range in files_list.
+
+void InputLine::init_completion()
+{
+ trim();
+ unistring &line = curr_para()->str;
+
+ // get directory, filename components
+ unistring filename;
+ u8string directory;
+
+ int i = cursor.pos - 1;
+ while (i >= 0 && line[i] != '/')
+ --i;
+ ++i; // move past '/'
+ // get the directory component
+ directory.init_from_unichars(line.begin(), i);
+ if (directory.empty())
+ directory = "./";
+ // get the filename component
+ filename.append(line.begin() + i, cursor.pos - i);
+
+ if (0) {
+ // if we haven't changed directory, no need to reread
+ // directory contents.
+ if (files_directory != directory) {
+ files_directory = directory;
+ get_directory_files(directory);
+ }
+ } else {
+ // All in all, the following is faster, because it
+ // doesn't stat(2) everything in the directory.
+ get_directory_files(directory, u8string(filename).c_str());
+ }
+
+ insertion_pos = cursor.pos;
+ prefix_len = filename.len();
+
+ slice_begin = slice_end = curr_choice = -1;
+ for (unsigned i = 0; i < files_list.size(); i++) {
+ unistring &entry = files_list[i];
+ if (entry.len() >= prefix_len)
+ if (std::equal(filename.begin(), filename.end(), entry.begin())) {
+ if (slice_begin == -1)
+ slice_begin = i;
+ slice_end = i;
+ }
+ }
+}
+
+// complete() - completes the filename under the cursor.
+//
+// @param forward - complete to the next filename if true; to the
+// previous one if false.
+
+void InputLine::complete(bool forward)
+{
+ // event_num and last_tab_event_num are used as a crude mechanism to
+ // determine if a TAB session has ended, e.g. as a result of a key
+ // other than TAB being pressed.
+ //
+ // On each event event_num is incremented (see handle_event()). if
+ // last_tab_event_num is event_num shy 1, it means our TAB session
+ // was not interrupted.
+
+ bool restart = (event_num - 1 != last_tab_event_num);
+ last_tab_event_num = event_num;
+
+ if (restart) {
+ // starts a TAB session.
+ init_completion();
+ // give warning when the length of the partial filename component
+ // is 0. This means that we browse through _all_ the files in
+ // the directory.
+ if (prefix_len == 0)
+ signal_error();
+ }
+
+ if (slice_begin == -1) {
+ // no filenames begin with the partial filename component.
+ signal_error();
+ return;
+ }
+
+ int prev_choice_len = 0;
+ if (curr_choice != -1)
+ prev_choice_len = files_list[curr_choice].size() - prefix_len;
+
+ // wrap around the completion slice if we bumped into its end.
+ if (curr_choice != -1)
+ curr_choice += forward ? 1 : -1;
+ if (curr_choice > slice_end || curr_choice < slice_begin)
+ curr_choice = -1;
+ if (curr_choice == -1)
+ curr_choice = forward ? slice_begin : slice_end;
+
+ cursor.pos = insertion_pos;
+ replace_text(files_list[curr_choice].begin() + prefix_len,
+ files_list[curr_choice].size() - prefix_len, prev_choice_len);
+
+ // a special case: if we've uniquely completed a direcory name, we want
+ // the next TAB to start a new TAB session to complete the files within.
+ if (slice_begin == slice_end
+ && *(files_list[curr_choice].end() - 1) == '/')
+ last_tab_event_num = -2;
+}
+
+// previous_completion() - interactive command to move backward in the
+// completion list. Usually bound to the M-TAB key.
+
+INTERACTIVE void InputLine::previous_completion()
+{
+ if (complete_type != cmpltOff)
+ complete(false);
+}
+
+// next_completion() - interactive command to move forward in the
+// completion list. Usually bound to the TAB key.
+
+INTERACTIVE void InputLine::next_completion()
+{
+ if (complete_type != cmpltOff)
+ complete(true);
+}
+
+bool InputLine::handle_event(const Event &evt)
+{
+ // Emulate contemporary GUIs, where the input is
+ // cleared on first letter typed.
+ if (event_num == 0 && evt.is_literal() && evt.ch != 13) {
+ // we don't use new_document() because we want to
+ // be able to undo this operation.
+ delete_paragraph();
+ }
+
+ if (event_num == 0)
+ request_update(rgnAll); // make sure do_syntax_highlight is called.
+
+ event_num++;
+
+ if (!Dispatcher::handle_event(evt))
+ return EditBox::handle_event(evt);
+ return false;
+}
+
+// do_syntax_highlight() - Emulate comtemporary GUIs: when event_num is 0,
+// select all the text to hint the user the next letter will erase everything.
+
+void InputLine::do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num)
+{
+ if (event_num == 0) {
+ attribute_t selected_attr = get_attr(EDIT_SELECTED_ATTR);
+ for (idx_t i = 0; i < str.len(); i++)
+ attributes[i] = selected_attr;
+ }
+}
+
+// trim() - removes the wspaces at the start and end of the text
+// the user typed.
+
+void InputLine::trim()
+{
+ unistring &line = curr_para()->str;
+ // delete wspaces at start of line
+ idx_t i = 0;
+ while (i < line.len() && BiDi::is_space(line[i]))
+ i++;
+ if (i) {
+ idx_t prev_pos = cursor.pos;
+ cursor.pos = 0;
+ delete_text(i);
+ if (prev_pos >= i)
+ cursor.pos = prev_pos - i;
+ request_update(rgnCurrent);
+ }
+ // delete wspaces at end of line
+ i = line.len() - 1;
+ while (i >= 0 && BiDi::is_space(line[i]))
+ i--;
+ i++;
+ if (i < line.len()) {
+ idx_t prev_pos = cursor.pos;
+ cursor.pos = i;
+ delete_text(line.len() - i);
+ if (prev_pos < i)
+ cursor.pos = prev_pos;
+ request_update(rgnCurrent);
+ }
+}
+
+INTERACTIVE void InputLine::end_modal()
+{
+ trim();
+ if (history) {
+ // User presses Enter.
+ // If the user has altered the default text, don't overwrite the
+ // history entry but create a new entry intead.
+ if (history_idx == 0 && get_text() != history->front()
+ && !history->front().empty())
+ history->insert(history->begin(), get_text());
+ else
+ update_history();
+ }
+ EditBox::end_modal();
+}
+
+void InputLine::update_history()
+{
+ if (history)
+ (*history)[history_idx] = get_text();
+}
+
+// previous_history() - interactive command to move to the previous history
+// entry. Usually bound to the "Arrow Up" or C-p key.
+
+INTERACTIVE void InputLine::previous_history()
+{
+ if (history && history_idx < (int)history->size() - 1) {
+ update_history();
+ history_idx++;
+ set_text((*history)[history_idx]);
+ event_num = 0;
+ }
+}
+
+// next_history() - interactive command to move to the next history
+// entry. Usually bound to the "Arrow Down" or C-n key.
+
+INTERACTIVE void InputLine::next_history()
+{
+ if (history && history_idx > 0) {
+ update_history();
+ history_idx--;
+ set_text((*history)[history_idx]);
+ event_num = 0;
+ }
+}
+
diff --git a/inputline.h b/inputline.h
new file mode 100644
index 0000000..82c3e5a
--- /dev/null
+++ b/inputline.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_INPUTLINE_H
+#define BDE_INPUTLINE_H
+
+#include <vector>
+
+#include "editbox.h"
+
+
+class InputLine : public EditBox {
+
+public:
+
+ typedef std::vector<unistring> StringArray;
+
+ // What kind of filenames, if any, to complete?
+ enum CompleteType {
+ cmpltOff, // turn off filename completion.
+ cmpltAll,
+ cmpltDirectories // complete only directory names.
+ // this is useful for, e.g., a "Change Dir:"
+ // input line.
+ };
+
+private:
+
+ unistring vis_label;
+ direction_t label_dir;
+ int label_width;
+
+ // Filename completion variables:
+
+ CompleteType complete_type;
+ StringArray files_list;
+ u8string files_directory; // the directory we're listing
+
+ int event_num;
+ int last_tab_event_num;
+
+ int insertion_pos;
+ int prefix_len;
+ int slice_begin;
+ int slice_end;
+ int curr_choice;
+
+ /*
+ A little example can help in explaining what each completion
+ variable means.
+
+ Let's suppose our input-line looks like:
+
+ ~/documents/no_ -cp862
+ ^-----------cursor
+
+ We press TAB and a "TAB session" starts:
+
+ 0. "no" is the partial filename component and "~/documents/" is the
+ directory name component.
+
+ 1. files_list is populated with all filenames in "~/documents":
+
+ 0. archive.txt
+ 1. notes.txt
+ 2. notes2.txt
+ 3. november.txt
+ 4. questions.txt
+
+ 2. only entries 1 to 3 are relevant in our TAB session (because
+ they start in "no"), so slice_begin is set to 1 and slice_end
+ to 3.
+
+ 3. insertion_pos is set to the cursor position.
+
+ 4. prefix_len is set to the length of the partial filename
+ component: 2. (that is, strlen("no")).
+
+ 5. While moving forward and backward in files_list (as a result
+ of pressing TAB and M-TAB), curr_choice is updated to point
+ to the current entry.
+ */
+
+ // History variables:
+
+ StringArray *history;
+ int history_idx;
+
+protected:
+
+ // Filename completion
+
+ void init_completion();
+ void get_directory_files(u8string directory, const char *prefix = NULL);
+ void complete(bool forward);
+
+ // History
+
+ void init_history(int history_set);
+ void update_history();
+
+ ////
+
+ void trim();
+
+protected:
+ virtual void do_syntax_highlight(const unistring &str,
+ AttributeArray &attributes, int para_num);
+
+public:
+
+ HAS_ACTIONS_MAP(InputLine, EditBox);
+ HAS_BINDINGS_MAP(InputLine, EditBox);
+
+ InputLine(const char *aLabel, const unistring &default_text,
+ int history_set = 0, CompleteType complete = cmpltOff);
+ void set_label(const char *aLabel);
+ void set_text(const unistring &s);
+ unistring get_text() { return curr_para()->str; }
+
+ INTERACTIVE void end_modal();
+ INTERACTIVE void next_completion();
+ INTERACTIVE void previous_completion();
+ INTERACTIVE void next_history();
+ INTERACTIVE void previous_history();
+
+ virtual void redraw_paragraph(Paragraph &p,
+ int window_start_line, bool only_cursor, int);
+
+ virtual bool handle_event(const Event &evt);
+};
+
+#endif
+
diff --git a/install-sh b/install-sh
new file mode 100755
index 0000000..e9de238
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. M.I.T. makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd="$cpprog"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ true
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ chmodcmd=""
+ else
+ instcmd=mkdir
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f $src -o -d $src ]
+ then
+ true
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ true
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ true
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ true
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ true
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+ $doit $rmcmd -f $dstdir/$dstfile &&
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
diff --git a/io.cc b/io.cc
new file mode 100644
index 0000000..82502f0
--- /dev/null
+++ b/io.cc
@@ -0,0 +1,470 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <fcntl.h> // file primitives
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h> // strerror
+#include <pwd.h> // getpwuid
+
+#include <map>
+
+#include "io.h"
+#include "editbox.h"
+#include "converters.h"
+#include "dbg.h"
+#include "speller.h" // for UNLOAD_SPELLER(), see TODO
+
+#define CONVBUFSIZ 8192
+
+static u8string err_msg;
+
+void set_last_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ err_msg.vcformat(fmt, ap);
+ va_end(ap);
+}
+
+void set_last_error(int err) {
+ err_msg = strerror(err);
+}
+
+const char *get_last_error() {
+ return err_msg.c_str();
+}
+
+static bool xload_file(EditBox *editbox,
+ int fd,
+ const char *specified_encoding,
+ const char *default_encoding,
+ u8string &effective_encoding)
+{
+ unichar outbuf[CONVBUFSIZ+1];
+ char inbuf[CONVBUFSIZ];
+ bool result = true;
+ const char *encoding = NULL; // silence the compiler
+ Converter *conv = NULL;
+
+ // we set "effective_encoding" here too, because the
+ // file might be empty and the next assignment won't be
+ // executed at all.
+ if (specified_encoding && *specified_encoding)
+ effective_encoding = specified_encoding;
+ else
+ effective_encoding = default_encoding;
+
+ size_t insize = 0;
+ size_t buf_file_offset = 0;
+ while (1) {
+ ssize_t nread;
+ char *inptr = inbuf;
+
+ nread = read(fd, inbuf + insize, sizeof(inbuf) - insize);
+ if (nread == 0) {
+ // no more input
+ break;
+ }
+ if (nread == -1) {
+ set_last_error(errno);
+ result = false;
+ break;
+ }
+ insize += nread;
+
+ // instantiate a Converter object
+ if (!conv) {
+ if (!specified_encoding || !*specified_encoding) {
+ const char *guess = guess_encoding(inbuf, insize);
+ if (guess)
+ encoding = guess;
+ else
+ encoding = default_encoding;
+ } else {
+ encoding = specified_encoding;
+ }
+ conv = ConverterFactory::get_converter_from(encoding);
+ if (!conv) {
+ set_last_error(_("Conversion from '%s' not available"),
+ encoding);
+ result = false;
+ break;
+ }
+ effective_encoding = encoding;
+ }
+
+ unichar *wrptr = outbuf;
+ // do the conversion
+ int nconv = conv->convert(&wrptr, &inptr, insize);
+
+ // load into editbox
+ editbox->transfer_data(outbuf, wrptr - outbuf);
+
+ if (nconv == -1) {
+ insize = inbuf + insize - inptr;
+ if (errno == EINVAL) {
+ // incomplete byte sequence. move the unused bytes
+ // to the beginning of the buffer. the next read()
+ // will complete the sequence.
+ memmove(inbuf, inptr, insize);
+ } else {
+ // invalid byte sequence.
+ if (errno == EILSEQ)
+ set_last_error(_("'%s' conversion failed at position %d"),
+ encoding, buf_file_offset + (inptr - inbuf));
+ else
+ set_last_error(_("'%s' conversion failed"), encoding);
+ result = false;
+ break;
+ }
+ } else {
+ insize = 0;
+ }
+ buf_file_offset += nread;
+ }
+
+ if (conv)
+ delete conv;
+
+ return result;
+}
+
+// xload_file() - loads a file into an EditBox buffer.
+
+bool xload_file(EditBox *editbox,
+ const char *filename,
+ const char *specified_encoding,
+ const char *default_encoding,
+ u8string &effective_encoding,
+ bool &is_new,
+ bool new_document)
+{
+ int fd;
+ bool is_pipe = false;
+ FILE *pipe_stream = NULL;
+ bool is_stdin = false;
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ is_stdin = true;
+
+ if (filename[0] == '|' || filename[0] == '!') {
+ filename++;
+ // we use UTF-8 for pipe communication
+ specified_encoding = "UTF-8";
+ is_pipe = true;
+ }
+
+ editbox->start_data_transfer(EditBox::dataTransferIn, new_document);
+
+ if (is_pipe) {
+ UNLOAD_SPELLER(); // see TODO
+ is_new = true;
+ pipe_stream = popen(filename, "r");
+ if (pipe_stream == NULL) {
+ editbox->end_data_transfer();
+ set_last_error(errno);
+ return false;
+ }
+ fd = fileno(pipe_stream);
+ } else {
+ is_new = false;
+ if (is_stdin)
+ fd = STDIN_FILENO;
+ else
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ editbox->end_data_transfer();
+ if (errno == ENOENT && new_document) {
+ is_new = true;
+ return true;
+ } else {
+ set_last_error(errno);
+ return false;
+ }
+ }
+ }
+
+ bool result = xload_file(editbox, fd, specified_encoding,
+ default_encoding, effective_encoding);
+ editbox->end_data_transfer();
+
+ if (is_pipe)
+ pclose(pipe_stream);
+ else {
+ if (!is_stdin)
+ close(fd);
+ }
+ return result;
+}
+
+static bool xsave_file(EditBox *editbox,
+ int fd,
+ const char *encoding,
+ unichar &offending_char)
+{
+ char outbuf[CONVBUFSIZ*6];
+ unichar inbuf[CONVBUFSIZ];
+ bool result = true;
+ Converter *conv = NULL;
+
+ conv = ConverterFactory::get_converter_to(encoding);
+
+ if (!conv) {
+ set_last_error(_("Conversion to '%s' not available"), encoding);
+ return false;
+ }
+
+ int edit_buf_offset = 0;
+ while (1) {
+ int nread = editbox->transfer_data(inbuf, sizeof(inbuf)/sizeof(unichar));
+ if (nread == 0) {
+ // We reached end of buffer.
+ // :TODO: zero output state.
+ break;
+ }
+
+ char *wrptr = outbuf;
+ unichar *inptr = inbuf;
+ // do the conversion
+ int nconv = conv->convert(&wrptr, &inptr, nread);
+
+ if (write(fd, outbuf, wrptr - outbuf) == -1) {
+ set_last_error(errno);
+ result = false;
+ break;
+ }
+
+ if (nconv == -1) {
+ // Probably some unicode character couldn't be converted
+ // to the requested encoding.
+ if (errno == EILSEQ) {
+ int illegal_pos = inptr - inbuf;
+ offending_char = inbuf[illegal_pos];
+ set_last_error(_("'%s' conversion failed at position "
+ "%d (char: U+%04X)"),
+ encoding, edit_buf_offset + illegal_pos,
+ offending_char);
+ } else {
+ set_last_error(_("'%s' conversion failed"), encoding);
+ }
+ result = false;
+ break;
+ }
+ edit_buf_offset += nread;
+ }
+
+ if (conv)
+ delete conv;
+
+ return result;
+}
+
+// xsave_file() - saves an EditBox buffer to a file.
+
+bool xsave_file(EditBox *editbox,
+ const char *filename,
+ const char *specified_encoding,
+ const char *backup_suffix,
+ unichar &offending_char,
+ bool selection_only)
+{
+ offending_char = 0;
+
+ int fd;
+ bool is_pipe = false;
+ FILE *pipe_stream = NULL;
+ u8string tmp_filename;
+ bool is_stdout = false;
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ is_stdout = true;
+
+ if (filename[0] == '|' || filename[0] == '!') {
+ filename++;
+ // we use UTF-8 for pipe communication
+ specified_encoding = "UTF-8";
+ is_pipe = true;
+ }
+
+ if (is_pipe) {
+ UNLOAD_SPELLER(); // see TODO
+ pipe_stream = popen(filename, "w");
+ if (pipe_stream == NULL) {
+ set_last_error(errno);
+ return false;
+ }
+ fd = fileno(pipe_stream);
+ } else if (is_stdout) {
+ fd = STDOUT_FILENO;
+ } else {
+ // 1. Get filename's permissions, if exists.
+ struct stat file_info;
+ mode_t permissions;
+ if (stat(filename, &file_info) != -1)
+ permissions = (file_info.st_mode & 0777);
+ else
+ permissions = 0666;
+
+ // 2. Create filename.tmp and write data into it.
+ // Use filename's permissions.
+ tmp_filename = filename;
+ tmp_filename += ".tmp";
+ fd = open(tmp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ permissions);
+ if (fd == -1) {
+ set_last_error(errno);
+ return false;
+ }
+ }
+
+ editbox->start_data_transfer(EditBox::dataTransferOut, false, selection_only);
+ bool result = xsave_file(editbox, fd, specified_encoding, offending_char);
+ editbox->end_data_transfer();
+
+ if (is_pipe) {
+ pclose(pipe_stream);
+ } else if (is_stdout) {
+ if (!result) {
+ return false;
+ }
+ } else {
+ if (!result) {
+ close(fd);
+ unlink(tmp_filename.c_str());
+ return false;
+ }
+ if (close(fd) == -1) {
+ set_last_error(errno);
+ unlink(tmp_filename.c_str());
+ return false;
+ }
+
+ // 3. if the user wants a backup,
+ // mv filename -> filename${backup_extension}
+ // We ignore errors, since filename may not exist.
+ if (backup_suffix && *backup_suffix) {
+ u8string backup = filename;
+ backup += backup_suffix;
+ rename(filename, backup.c_str());
+ }
+ // Else, unlink filename
+ else {
+ unlink(filename);
+ }
+
+ // 4. rename filename.tmp to filename
+ if (rename(tmp_filename.c_str(), filename) == -1) {
+ set_last_error(errno);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// has_prog() returns true if progname is in the PATH and is executable.
+
+bool has_prog(const char *progname)
+{
+ const char *path = getenv("PATH");
+ if (!path) return false;
+ const char *pos = path;
+ while (pos) {
+ u8string comp;
+ pos = strchr(path, ':');
+ if (!pos)
+ comp = u8string(path);
+ else
+ comp = u8string(path, pos);
+ comp += u8string("/") + progname;
+ if (access(comp.c_str(), X_OK) == 0) {
+ //cerr << "found [" << comp.c_str() << "]\n";
+ return true;
+ }
+ path = pos + 1;
+ }
+ return false;
+}
+
+// expand_tilde() - do tilde expansion in filenames.
+
+void expand_tilde(u8string &filename)
+{
+ int slash_pos = filename.index('/');
+
+ if (slash_pos == -1 || filename[0] != '~')
+ return; // nothing to do
+
+ u8string tilde_prefix(&*(filename.begin() + 1),
+ &*(filename.begin() + slash_pos));
+ u8string user_home;
+
+ if (tilde_prefix == "") {
+ // handle "~/" - the user executing us.
+ if (getenv("HOME"))
+ user_home = getenv("HOME");
+ else
+ user_home = getpwuid(geteuid())->pw_dir;
+ } else {
+ // handle "~username/"
+ struct passwd *user_info;
+ if ((user_info = getpwnam(tilde_prefix.c_str()))) {
+ user_home = user_info->pw_dir;
+ }
+ }
+
+ // if we've found the user's home, replace tilde_prefix with it.
+ if (!user_home.empty()) {
+ if (*(user_home.end() - 1) != '/')
+ user_home += "/";
+ filename.replace(0, tilde_prefix.size() + 2, user_home);
+ }
+}
+
+struct ltstr {
+ bool operator() (const char *s1, const char *s2) const {
+ return strcmp(s1, s2) < 0;
+ }
+};
+
+// get_cfg_filename() - does "%P" and tilde expansion on a package
+// pathname. in other words, converts a package pathname (~/%P/name)
+// to a filesystem pathname (/home/david/geresh/name).
+
+const char *get_cfg_filename(const char *tmplt)
+{
+ // save the expansion result in a hash
+ static std::map<const char *, u8string, ltstr> filenames_cache;
+
+ if (filenames_cache.find(tmplt) == filenames_cache.end()) {
+ u8string filename = tmplt;
+ // replace all "%P" with PACKAGE
+ const char *packpos;
+ while ((packpos = strstr(filename.c_str(), "%P")) != NULL)
+ filename.replace(packpos - filename.c_str(), 2, PACKAGE);
+
+ expand_tilde(filename);
+ filenames_cache[tmplt] = filename;
+ }
+ return filenames_cache[tmplt].c_str();
+}
+
diff --git a/io.h b/io.h
new file mode 100644
index 0000000..2965b9d
--- /dev/null
+++ b/io.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_IO_H
+#define BDE_IO_H
+
+#include "types.h"
+
+class EditBox;
+
+bool xload_file(EditBox *editbox,
+ const char *filename,
+ const char *specified_encoding,
+ const char *default_encoding,
+ u8string &effective_encoding,
+ bool &is_new,
+ bool new_document);
+
+bool xsave_file(EditBox *editbox,
+ const char *filename,
+ const char *specified_encoding,
+ const char *backup_suffix,
+ unichar &offending_char,
+ bool selection_only = false);
+
+void set_last_error(const char *fmt, ...);
+void set_last_error(int err);
+const char *get_last_error();
+
+bool has_prog(const char *progname);
+void expand_tilde(u8string &filename);
+const char *get_cfg_filename(const char *tmplt);
+
+#endif
+
diff --git a/iso88598.cc b/iso88598.cc
new file mode 100644
index 0000000..717a4cb
--- /dev/null
+++ b/iso88598.cc
@@ -0,0 +1,111 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "iso88598.h"
+
+#include "univalues.h"
+
+#define ISO88598_HEB_ALEF 0xE0
+#define ISO88598_HEB_TAV 0xFA
+
+// The following 7 codes were taken from FriBiDi's fribidi_char_sets_iso8859_8.c.
+// These are "proposed extensions to ISO-8859-8."
+
+#define ISO88598_LRM 0xFD
+#define ISO88598_RLM 0xFE
+#define ISO88598_LRE 0xFB
+#define ISO88598_RLE 0xFC
+#define ISO88598_PDF 0xDD
+#define ISO88598_LRO 0xDB
+#define ISO88598_RLO 0xDC
+
+
+// int unicode_to_iso88598(unichar ch)
+//
+// This is a fallback function used for saving files when no iconv
+// implementation is present.
+//
+// It returns EOF when the unicode character can't be represented in
+// ISO-8859-9.
+
+int unicode_to_iso88598(unichar ch)
+{
+ if (ch <= 0xA0)
+ return ch;
+ if (ch >= UNI_HEB_ALEF && ch <= UNI_HEB_TAV)
+ return ISO88598_HEB_ALEF + (ch - UNI_HEB_ALEF);
+ switch (ch) {
+ case 0x00D7: return 0xAA; // MULTIPLICATION SIGN
+ case 0x00F7: return 0xBA; // DIVISION SIGN
+ case 0x2017: return 0xDF; // DOUBLE LOW LINE
+ }
+ if (ch >= 0xA2 && ch <= 0xBE)
+ return ch;
+
+ // The following are non-standard conversions.
+
+ switch (ch) {
+ case UNI_LRM: return ISO88598_LRM;
+ case UNI_RLM: return ISO88598_RLM;
+ case UNI_LRE: return ISO88598_LRE;
+ case UNI_RLE: return ISO88598_RLE;
+ case UNI_PDF: return ISO88598_PDF;
+ case UNI_LRO: return ISO88598_LRO;
+ case UNI_RLO: return ISO88598_RLO;
+ }
+
+ return EOF;
+}
+
+// unichar iso88598_to_unicode(unsigned char ch)
+//
+// This is a fallback function used for loading files when no iconv
+// implementation is present.
+//
+// It returns the Unicode Replacement Character when it encounters a
+// character which is illegal in ISO-8859-9.
+
+unichar iso88598_to_unicode(unsigned char ch)
+{
+ if (ch <= 0xA0)
+ return ch;
+ if (ch >= ISO88598_HEB_ALEF && ch <= ISO88598_HEB_TAV)
+ return UNI_HEB_ALEF + (ch - ISO88598_HEB_ALEF);
+ switch (ch) {
+ case 0xAA: return 0x00D7; // MULTIPLICATION SIGN
+ case 0xBA: return 0x00F7; // DIVISION SIGN
+ case 0xDF: return 0x2017; // DOUBLE LOW LINE
+ }
+ if (ch >= 0xA2 && ch <= 0xBE)
+ return ch;
+
+ // The following are non-standard conversions.
+
+ switch (ch) {
+ case ISO88598_LRM: return UNI_LRM;
+ case ISO88598_RLM: return UNI_RLM;
+ case ISO88598_LRE: return UNI_LRE;
+ case ISO88598_RLE: return UNI_RLE;
+ case ISO88598_PDF: return UNI_PDF;
+ case ISO88598_LRO: return UNI_LRO;
+ case ISO88598_RLO: return UNI_RLO;
+ }
+
+ return UNI_REPLACEMENT;
+}
+
diff --git a/iso88598.h b/iso88598.h
new file mode 100644
index 0000000..f6c298f
--- /dev/null
+++ b/iso88598.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_ISO88598_H
+#define BDE_ISO88598_H
+
+#include "types.h"
+
+int unicode_to_iso88598(unichar ch);
+unichar iso88598_to_unicode(unsigned char ch);
+
+#endif
+
diff --git a/kbdtab b/kbdtab
new file mode 100644
index 0000000..4efd3c1
--- /dev/null
+++ b/kbdtab
@@ -0,0 +1,46 @@
+# Hebrew keyboard emulation.
+#
+# You may change this file to emulate your own national keyboard.
+
+'`' ';'
+'q' '/'
+'w' '''
+'e' 'ק'
+'r' 'ר'
+'t' 'א'
+'y' 'ט'
+'u' 'ו'
+'i' 'ן'
+'o' 'ם'
+'p' 'פ'
+'a' 'ש'
+'s' 'ד'
+'d' 'ג'
+'f' 'כ'
+'g' 'ע'
+'h' 'י'
+'j' 'ח'
+'k' 'ל'
+'l' 'ך'
+';' 'ף'
+''' ','
+'z' 'ז'
+'x' 'ס'
+'c' 'ב'
+'v' 'ה'
+'b' 'נ'
+'n' 'מ'
+'m' 'צ'
+',' 'ת'
+'.' 'ץ'
+'/' '.'
+
+'(' ')'
+')' '('
+'[' ']'
+']' '['
+'{' '}'
+'}' '{'
+'<' '>'
+'>' '<'
+
diff --git a/label.cc b/label.cc
new file mode 100644
index 0000000..79e560b
--- /dev/null
+++ b/label.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "label.h"
+#include "themes.h"
+
+Label::Label()
+{
+ create_window();
+ is_highlighted = false;
+ dirty = true;
+}
+
+Label::Label(const char *aText)
+{
+ create_window();
+ is_highlighted = false;
+ set_text(aText);
+}
+
+void Label::set_text(const char *aText)
+{
+ text = aText ? aText : "";
+ // we append one space so that if this widget gets the focus,
+ // the terminal cursor won't stick to the text. this is for
+ // aesthetic only.
+ text += ' ';
+ dirty = true;
+}
+
+void Label::highlight(bool value)
+{
+ is_highlighted = value;
+ invalidate_view();
+}
+
+void Label::update()
+{
+ if (!dirty)
+ return;
+ wbkgd(wnd, get_attr(is_highlighted ? STATUSLINE_ATTR : DIALOGLINE_ATTR));
+ wmove(wnd, 0, 0);
+ wclrtoeol(wnd);
+ draw_string(text.c_str(), true);
+ wnoutrefresh(wnd);
+ dirty = false;
+}
+
+void Label::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ dirty = true;
+}
+
diff --git a/label.h b/label.h
new file mode 100644
index 0000000..832a96a
--- /dev/null
+++ b/label.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_LABEL_H
+#define BDE_LABEL_H
+
+#include "types.h"
+#include "widget.h"
+
+// Label is a simple widget that displays a string
+
+class Label : public Widget {
+
+ u8string text;
+ bool dirty;
+ bool is_highlighted;
+
+public:
+
+ Label();
+ Label(const char *aText);
+
+ void set_text(const char *aText);
+ void highlight(bool value = true);
+ bool empty() const { return (text.size() == 0); }
+ virtual void update();
+ virtual void invalidate_view() { dirty = true; }
+ virtual bool is_dirty() const { return dirty; }
+ virtual void resize(int lines, int columns, int y, int x);
+};
+
+#endif
+
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..36ed7e7
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,820 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+// Baruch says to put io.h first.
+#include "io.h" // get_cfg_filename
+#include "pathnames.h"
+#include "terminal.h"
+#include "editor.h"
+#include "themes.h"
+#include "directvect.h"
+#include "dbg.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h> // strtol
+#include <ctype.h> // isspace
+#include <unistd.h> // getopt
+#ifdef HAVE_GETOPT_LONG
+# include <getopt.h>
+#endif
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+typedef DirectVector<char *> ArgsList;
+
+static int get_num(const char *optname, const u8string &arg, int min, int max)
+{
+ char *endptr;
+ int value = strtol(arg.c_str(), &endptr, 10);
+ if (*endptr != '\0' || value < min || value > max)
+ fatal(_("Invalid argument for option `%s'. "
+ "Valid argument is a numeric value from %d to %d\n"),
+ optname, min, max);
+ return value;
+}
+
+static bool get_bool(const char *optname, const u8string &arg)
+{
+ bool result = false;
+ if (arg == "on" || arg == "yes" || arg == "true")
+ result = true;
+ else if (arg == "off" || arg == "no" || arg == "false")
+ result = false;
+ else
+ fatal(_("Invalid argument for option `%s'. "
+ "Valid argument is either `on' or `off'.\n"),
+ optname);
+ return result;
+}
+
+static void help()
+{
+ printf(_("Usage: %s [options] [[+LINE] file]\n"), PACKAGE);
+
+ printf(_("\nText display:\n"
+ " -a, --dir-algo WORD Use the WORD algorithm in determining\n"
+ " the base direction (aka \"paragraph\n"
+ " embedding level\") of each paragraph:\n"
+ " `unicode', `contextual-strong',\n"
+ " `contextual-rtl', `ltr', `rtl'\n"
+ " (default: `contextual-rtl')\n"
+ " -D, --bidi BOOL Enable/disable BiDi\n"
+ " -m, --maqaf WORD How to display the Hebrew makaf:\n"
+ " `asis', `ascii', `highlight'\n"
+ " (default: `highlight')\n"
+ " -P, --points WORD How to display Hebrew/Arabic points:\n"
+ " `off', `transliterate', `asis'\n"
+ " (default: `transliterate')\n"
+ " -M, --show-formatting BOOL Use ASCII characters to represent BiDi\n"
+ " formatting codes, paragraph endings and\n"
+ " tabs (default: on)\n"
+ " -A, --arabic-shaping BOOL Use contextual presentation forms to\n"
+ " display arabic\n"
+ " -T, --tabsize COLS Set tab size to COLS (default: 8)\n"
+ " (synonyms: --tab-width, --tab-size)\n"
+ " -W, --wrap WORD Set wrap policy to:\n"
+ " `off', `atwhitespace', `anywhere'\n"
+ " (default: `atwhitespace')\n"
+ " -Q, --scrollbar WORD Whether and where to display a scrollbar:\n"
+ " `none', `left', `right'\n"
+ " (default: `none')\n"
+ " -G, --syntax BOOL Auto-detect syntax (for highlighting)\n"
+ " (default: on)\n"
+ " -U, --underline BOOL Highlight *text* and _text_\n"
+ " (default: on)\n"
+ " -g, --theme FILE Theme to use. FILE should be just\n"
+ " the base file name, with no directory\n"
+ " component.\n"
+ " (e.g.: --theme green.thm)\n"));
+
+ printf(_("\nEditing:\n"
+ " -i, --auto-indent BOOL Automatically indent new lines\n"
+ " -j, --auto-justify BOOL Automatically justify long lines\n"
+ " -J, --justification-column COLS\n"
+ " Set justification column to COLS\n"
+ " (default: 72)\n"
+ " -e, --rfc2646-trailing-space BOOL\n"
+ " Keep a trailing space on each line when\n"
+ " justifying paragraphs (default: on)\n"
+ " -k, --key-for-key-undo BOOL Allow user to undo discrete keys\n"
+ " (default: off)\n"
+ " -u, --undo-size SIZE Limit undo stack to SIZE kilo-bytes\n"
+ " (default: 50k)\n"
+ " -q, --smart-typing BOOL Replace some plain ASCII characters with\n"
+ " typographical ones.\n"
+ ));
+
+ printf(_("\nFiles:\n"
+ " -f, --default-file-encoding ENC\n"
+ " Set default file encoding to ENC\n"
+ " -F, --file-encoding ENC Load the file (specified at the command\n"
+ " line) using the ENC encoding\n"
+ " -S, --suffix SFX Set file backup extension. Set to\n"
+ " empty string if you don't want backup.\n"
+ " (default: read the SIMPLE_BACKUP_SUFFIX\n"
+ " environment variable if exists.\n"
+ " If it doesn't, use \"~\")\n"));
+
+ printf(_("\nTerminal capabilities:\n"
+ " -b, --bw BOOL Don't use colors\n"
+ " -B, --big-cursor BOOL Use a big cursor in the console, if possible\n"
+ " -C, --combining-term BOOL Assume the terminal is capable of displaying\n"
+ " combining characters and wide (Asian)\n"
+ " characters\n"
+ " -H, --iso88598-term BOOL Assume the terminal uses the ISO-8859-8\n"
+ " encoding (only when not using wide-curses).\n"
+ " -Y, --graphical-boxes WORD Box characters to use for the pulldown menu\n"
+ " ans the scrollbar. Valid options:\n"
+ " `ascii', `graphical', `auto'\n"
+ " (default: `auto', which means that if the\n"
+ " DISPLAY environment variable is\n"
+ " set then use graphical chars)\n"));
+
+ printf(_("\nSpeller:\n"
+ " -Z, --speller-cmd CMD The speller command\n"
+ " (default: \"ispell -a\")\n"
+ " -X, --speller-encoding ENC The encoding with which to\n"
+ " communicate with the speller.\n"
+ " (default: ISO-8859-1)\n"
+ " (if hspell is found, and no speller command is specified, Geresh\n"
+ " will use it using the correct configuration automatically.)\n"));
+
+ printf(_("\nLog2Vis:\n"
+ " -p, --log2vis log2vis mode.\n"
+ " -E, --log2vis-options OPTS The option argument OPTS may include:\n"
+ " `bdo' - to contain the line between\n"
+ " LRO and PDF marks.\n"
+ " `nopad' - do not pad RTL lines on\n"
+ " the left.\n"
+ " `engpad' - pad LTR lines on the\n"
+ " right.\n"
+ " `emph[:glyph[:marker]]' - turn on\n"
+ " underlining.\n"
+ " -t, --log2vis-output-encoding ENC\n"
+ " Set output encoding for log2vis\n"
+ " (default: the encoding in which the\n"
+ " file was read.)\n"
+ " -w NUM Wrap paragraphs at the NUM'th column.\n"
+ " use `-w0' to turn wrapping off.\n"
+ " (default: use the COLUMNS environment\n"
+ " variable, or 80)\n"));
+
+ printf(_("\nMiscellaneous:\n"
+ " -x, --extrnal-editor CMD External editor\n"
+ " -c, --show-cursor-position BOOL\n"
+ " Constantly display the cursor coordinates\n"
+ //" -n, --show-numbers BOOL Show line numbers\n"
+ " -v, -R, --read-only Read-only mode\n"
+ " -s, --scroll-step LINES Set scroll-step to LINES lines\n"
+ " (default: 1)\n"
+ " -L, --visual-cursor BOOL Visual cursor movement\n"
+ " (default: off)\n"
+ " +LINE Go to line LINE\n"
+ " -V, --version Print version info and editor capabilities\n"
+ " -h, --help Print usage information\n"));
+
+ printf(_("\nPlease read the User Manual for more information.\n"));
+ exit(0);
+}
+
+static void parse_rc_line(ArgsList &arguments, const char *s)
+{
+ const char *token_start = NULL;
+ bool in_quote = false;
+ if (!s || *s == '#')
+ return;
+
+ do {
+ if (*s && !isspace(*s) && !token_start) {
+ // starting a new token
+ token_start = s;
+ }
+ if (*s == '\'' || *s == '\"')
+ in_quote = !in_quote;
+ if (token_start && ((isspace(*s) && !in_quote) || !*s)) {
+ // we've found a token.
+ // allocate memory for it and append it
+ // to the argument list.
+ char *param = new char[s - token_start + 1];
+ arguments.push_back(param);
+ // we don't preserve the quoting characters.
+ for (const char *tp = token_start; tp < s; tp++)
+ if (*tp != '\'' && *tp != '\"')
+ *param++ = *tp;
+ *param = '\0';
+ token_start = NULL;
+ }
+ } while (*s++);
+}
+
+static bool parse_rc_file(ArgsList &arguments, const char *filename)
+{
+#define MAX_LINE_LEN 1024
+ FILE *fp;
+ if ((fp = fopen(filename, "r")) != NULL) {
+ DBG(1, ("Reading rc file %s\n", filename));
+ char line[MAX_LINE_LEN];
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ if (strchr(line, '#'))
+ *(strchr(line, '#')) = '\0';
+ parse_rc_line(arguments, line);
+ }
+ fclose(fp);
+ return true;
+ } else {
+ return false;
+ }
+#undef MAX_LINE_LEN
+}
+
+int main(int argc, char *argv[])
+{
+ set_debug_level(getenv("GERESH_DEBUG_LEVEL")
+ ? atoi(getenv("GERESH_DEBUG_LEVEL")) : 0);
+
+ DBG(1, ("\n\n----starting----\n\n"));
+
+#ifdef HAVE_SETLOCALE
+ if (!setlocale(LC_ALL, ""))
+ fatal(_("setlocale() failed! Please check your environment variables "
+ "LC_ALL, LC_CTYPE, LANG, etc.\n"));
+#endif
+
+ terminal::determine_locale();
+
+#ifdef USE_GETTEXT
+ // We enable gettext() only in the UTF-8 locale.
+ // That's because gettext() returns text encoded in the locale,
+ // whereas all the functions in this app expect UTF-8 text.
+ // I'm not going to "fix" this -- UTF-8 locales are the way to go!
+ //
+ // UPDATE: I can use bind_textdomain_codeset()
+ if (terminal::is_utf8) {
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ }
+#endif
+
+ /*
+ * Step 1.
+ *
+ * Build a new argv, which contains arguments read from the RC file,
+ * from the command line and from the environment variable $PACKAGE_ARGS.
+ *
+ */
+
+ ArgsList arguments;
+ arguments.push_back(PACKAGE); // push argv[0] (our program name)
+
+ // Read arguments from the RC file.
+ // first, read the RC file pointed to by $PACKAGE_RC.
+ // if that fails, read the user's personal RC file, and
+ // if that fails too, read the system wide RC file.
+
+ if (!(getenv(PACKAGE "_RC")
+ && parse_rc_file(arguments, get_cfg_filename(getenv(PACKAGE "_RC")))))
+ {
+ if (!parse_rc_file(arguments, get_cfg_filename(USER_RC_FILE)))
+ parse_rc_file(arguments, get_cfg_filename(SYSTEM_RC_FILE));
+ }
+
+ // append the original command line arguments
+ arguments.insert(arguments.end(), argv + 1, argv + argc);
+
+ // and finally append all arguments in $PACKAGE_ARGS
+ if (getenv(PACKAGE "_ARGS"))
+ parse_rc_line(arguments, getenv(PACKAGE "_ARGS"));
+
+ // the standard requires that argv[argc] == NULL
+ arguments.push_back(NULL);
+
+ argv = arguments.begin();
+ argc = arguments.size() - 1;
+
+ for (int i = 0; i < argc; i++) {
+ DBG(1, ("[cmd: %s]\n", argv[i]));
+ }
+
+ /*
+ * Step 2.
+ *
+ * Parse all the arguments using getopt().
+ *
+ */
+
+ // The following variables hold the values parsed from the
+ // arguments. We initialize them to default values.
+
+ bool auto_justify_flag = false;
+ bool auto_indent_flag = false;
+ bool enable_bidi = true;
+ bool show_formatting_flag = true;
+ bool show_cursor_position_flag = false;
+ bool show_numbers_flag = false; // TODO
+ bool read_only_flag = false;
+ int undo_size = 50;
+ bool key_for_key_undo_flag = false;
+ bool combining_term_flag = false;
+ bool combining_term_flag_specified = false;
+ bool iso88598_term_flag = false;
+ bool big_cursor_flag = false;
+ bool arabic_shaping_flag = false;
+ bool bw_flag = false;
+ int tab_width = 8;
+ int justification_column = 72;
+ int scroll_step = 1;
+ bool smart_typing_flag = false;
+ bool rfc2646_trailing_space_flag = true;
+ bool do_log2vis = false;
+ int non_interactive_text_width = 80;
+ EditBox::WrapType
+ wrap_type = EditBox::wrpAtWhiteSpace;
+ diralgo_t
+ dir_algo = algoContextRTL;
+ EditBox::rtl_nsm_display_t rtl_nsm = EditBox::rtlnsmTransliterated;
+ EditBox::maqaf_display_t maqaf = EditBox::mqfHighlighted;
+ Editor::scrollbar_pos_t scrollbar_pos = Editor::scrlbrNone;
+ const char
+ *file_encoding = NULL;
+ const char
+ *default_file_encoding = NULL;
+ const char
+ *log2vis_output_encoding = NULL;
+ const char
+ *log2vis_options = NULL;
+ const char
+ *backup_suffix = getenv("SIMPLE_BACKUP_SUFFIX")
+ ? getenv("SIMPLE_BACKUP_SUFFIX") : "~";
+ bool
+ print_version_flag = false;
+ const char
+ *speller_cmd = ""; // "ispell -a";
+ const char
+ *speller_encoding = ""; // "ISO-8859-1";
+ const char
+ *external_editor = "";
+ const char
+ *theme = NULL;
+ bool syntax_auto_detection = true;
+ bool underline = true;
+ bool visual_cursor_movement = false;
+
+ enum { boxesGraphical, boxesAscii, boxesAuto } graphical_boxes = boxesAuto;
+
+#ifdef HAVE_GETOPT_LONG
+ static struct option long_options[] = {
+ { "tab-width", 1, 0, 'T' },
+ { "tabwidth", 1, 0, 'T' },
+ { "tab-size", 1, 0, 'T' },
+ { "tabsize", 1, 0, 'T' },
+ { "justification-column", 1, 0, 'J' },
+ { "rfc2646-trailing-space", 1, 0, 'e' },
+ { "wrap", 1, 0, 'W' },
+ { "dir-algo", 1, 0, 'a' },
+ { "undo-size", 1, 0, 'u' },
+ { "key-for-key-undo", 1, 0, 'k' },
+ { "auto-justify", 1, 0, 'j' },
+ { "auto-indent", 1, 0, 'i' },
+ { "show-formatting",1, 0, 'M' },
+ { "maqaf", 1, 0, 'm' },
+ { "points", 1, 0, 'P' },
+ { "show-cursor-position", 1, 0, 'c' },
+ { "show-numbers", 1, 0, 'n' },
+ { "smart-typing", 1, 0, 'q' },
+ { "default-file-encoding", 1, 0, 'f' },
+ { "file-encoding", 1, 0, 'F' },
+ { "combining-term", 1, 0, 'C' },
+ { "iso88598-term", 1, 0, 'H' },
+ { "arabic-shaping", 1, 0, 'A' },
+ { "read-only", 0, 0, 'R' },
+ { "suffix", 1, 0, 'S' },
+ { "bw", 1, 0, 'b' },
+ { "big-cursor", 1, 0, 'B' },
+ { "version", 0, 0, 'V' },
+ { "help", 0, 0, 'h' },
+ { "scroll-step", 1, 0, 's' },
+ { "log2vis", 0, 0, 'p' },
+ { "log2vis-output-encoding", 1, 0, 't' },
+ { "log2vis-options", 1, 0, 'E' },
+ { "speller-cmd", 1, 0, 'Z' },
+ { "speller-encoding", 1, 0, 'X' },
+ { "graphical-boxes", 1, 0, 'Y' },
+ { "scrollbar", 1, 0, 'Q' },
+ { "bidi", 1, 0, 'D' },
+ { "syntax", 1, 0, 'G' },
+ { "underline", 1, 0, 'U' },
+ { "visual-cursor", 1, 0, 'L' },
+ { "theme", 1, 0, 'g' },
+ { "external-editor", 1, 0, 'x' },
+ {0, 0, 0, 0}
+ };
+#endif
+
+ if (getenv("COLUMNS"))
+ non_interactive_text_width = atoi(getenv("COLUMNS"));
+
+ const char *short_options = "T:e:J:W:w:a:A:k:S:s:u:j:i:P:M:m:c:n:q:f:F:C:H:RvB:b:Vhpt:E:Z:X:Y:Q:D:G:g:U:L:x:";
+ int c;
+#ifdef HAVE_GETOPT_LONG
+ int long_idx = -1;
+ while ((c = getopt_long(argc, argv, short_options,
+ long_options, &long_idx)) != -1) {
+#else
+ while ((c = getopt(argc, argv, short_options)) != -1) {
+#endif
+
+ u8string optname, arg;
+
+#ifdef HAVE_GETOPT_LONG
+ if (long_idx != -1) {
+ optname = "--";
+ optname += long_options[long_idx].name;
+ } else
+#endif
+ {
+ optname = "-";
+ optname += (char)c;
+ }
+
+ arg = optarg ? optarg : "";
+
+#define GET_BOOL() get_bool(optname.c_str(), arg)
+#define GET_NUM(min, max) get_num(optname.c_str(), arg, min, max)
+ switch (c) {
+ case 'j': auto_justify_flag = GET_BOOL(); break;
+ case 'i': auto_indent_flag = GET_BOOL(); break;
+ case 'M': show_formatting_flag = GET_BOOL(); break;
+ case 'c': show_cursor_position_flag = GET_BOOL(); break;
+ case 'n': show_numbers_flag = GET_BOOL(); break;
+ case 'q': smart_typing_flag = GET_BOOL(); break;
+ case 'C':
+ combining_term_flag_specified = true;
+ combining_term_flag = GET_BOOL();
+ break;
+ case 'H':
+#ifdef HAVE_WIDE_CURSES
+ fatal(_("The `%s' option is now valid only when NOT "
+ "compiling Geresh against wide-curses. (Why do "
+ "you want to use it?)\n"),
+ optname.c_str());
+#endif
+ iso88598_term_flag = GET_BOOL();
+ break;
+ case 'b': bw_flag = GET_BOOL(); break;
+ case 'A': arabic_shaping_flag = GET_BOOL(); break;
+ case 'B': big_cursor_flag = GET_BOOL(); break;
+ case 'k': key_for_key_undo_flag = GET_BOOL(); break;
+ case 'R':
+ case 'v':
+ read_only_flag = true;
+ break;
+ case 'T': tab_width = GET_NUM(1, 80); break;
+ case 'J': justification_column = GET_NUM(1, 999999); break;
+ case 'e': rfc2646_trailing_space_flag = GET_BOOL(); break;
+ case 'u': undo_size = GET_NUM(0, 999); break;
+ case 's': scroll_step = GET_NUM(1, 999); break;
+ case 'D': enable_bidi = GET_BOOL(); break;
+ case 'G': syntax_auto_detection = GET_BOOL(); break;
+ case 'U': underline = GET_BOOL(); break;
+ case 'L': visual_cursor_movement = GET_BOOL(); break;
+
+ case 'W':
+ if (arg == "off")
+ wrap_type = EditBox::wrpOff;
+ else if (arg == "atwhitespace")
+ wrap_type = EditBox::wrpAtWhiteSpace;
+ else if (arg == "anywhere")
+ wrap_type = EditBox::wrpAnywhere;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `off'\n"
+ " - `atwhitespace'\n"
+ " - `anywhere'\n"));
+ break;
+ case 'a':
+ if (arg == "unicode")
+ dir_algo = algoUnicode;
+ else if (arg == "contextual-strong")
+ dir_algo = algoContextStrong;
+ else if (arg == "contextual-rtl")
+ dir_algo = algoContextRTL;
+ else if (arg == "ltr")
+ dir_algo = algoForceLTR;
+ else if (arg == "rtl")
+ dir_algo = algoForceRTL;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `unicode'\n"
+ " - `contextual-strong'\n"
+ " - `contextual-rtl'\n"
+ " - `ltr'\n"
+ " - `rtl'\n"));
+ break;
+ case 'm':
+ if (arg == "asis")
+ maqaf = EditBox::mqfAsis;
+ else if (arg == "ascii")
+ maqaf = EditBox::mqfTransliterated;
+ else if (arg == "highlight")
+ maqaf = EditBox::mqfHighlighted;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `asis'\n"
+ " - `ascii'\n"
+ " - `highlight'\n"));
+ break;
+ case 'P':
+ if (arg == "off")
+ rtl_nsm = EditBox::rtlnsmOff;
+ else if (arg == "transliterate")
+ rtl_nsm = EditBox::rtlnsmTransliterated;
+ else if (arg == "asis")
+ rtl_nsm = EditBox::rtlnsmAsis;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `off'\n"
+ " - `transliterate'\n"
+ " - `asis'\n"));
+ break;
+ case 'Y':
+ if (arg == "graphical")
+ graphical_boxes = boxesGraphical;
+ else if (arg == "ascii")
+ graphical_boxes = boxesAscii;
+ else if (arg == "auto")
+ graphical_boxes = boxesAuto;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `graphical'\n"
+ " - `ascii'\n"
+ " - `auto'\n"));
+ break;
+ case 'Q':
+ if (arg == "none")
+ scrollbar_pos = Editor::scrlbrNone;
+ else if (arg == "left")
+ scrollbar_pos = Editor::scrlbrLeft;
+ else if (arg == "right")
+ scrollbar_pos = Editor::scrlbrRight;
+ else
+ fatal(_("invalid argument `%s' for `%s'\n"
+ "Valid arguments are:\n"
+ "%s"),
+ optarg, optname.c_str(),
+ _(" - `none'\n"
+ " - `left'\n"
+ " - `right'\n"));
+ break;
+ case 'F': file_encoding = optarg; break;
+ case 'f': default_file_encoding = optarg; break;
+ case 't': log2vis_output_encoding = optarg; break;
+ case 'S': backup_suffix = optarg; break;
+ case 'p':
+ do_log2vis = true;
+ arabic_shaping_flag = true;
+ show_formatting_flag = false;
+ rtl_nsm = EditBox::rtlnsmAsis;
+ break;
+ case 'E': log2vis_options = optarg; break;
+ case 'w': non_interactive_text_width = GET_NUM(0, 9999); break;
+ case 'Z': speller_cmd = optarg; break;
+ case 'X': speller_encoding = optarg; break;
+ case 'g': theme = optarg; break;
+ case 'x': external_editor = optarg; break;
+ case 'V': print_version_flag = true; break;
+ case 'h': help(); break;
+ case '?':
+ fatal(NULL);
+ break;
+ }
+#ifdef HAVE_GETOPT_LONG
+ long_idx = -1;
+#endif
+ }
+#undef GET_BOOL
+#undef GET_NUM
+
+ if (non_interactive_text_width == 0)
+ wrap_type = EditBox::wrpOff;
+
+ /*
+ * Step 3.
+ *
+ * Initialize the terminal.
+ *
+ */
+
+ if (iso88598_term_flag) {
+ terminal::is_utf8 = false;
+ terminal::force_iso88598 = true;
+ terminal::is_fixed = true;
+ }
+ if (combining_term_flag_specified) {
+ terminal::is_fixed = !combining_term_flag;
+ }
+
+#ifndef HAVE_WIDE_CURSES
+ if (terminal::is_utf8)
+ fatal(_("Terminal locale is UTF-8, but %s was not compiled with a "
+ "curses library that supports wide characters (ncursesw). "
+ "Please read the INSTALL document that accompanies Geresh "
+ "to get more information.\n"), PACKAGE);
+#endif
+
+ // the -V option ("print version") is very useful when we want to
+ // find out what the program thinks our terminal capabilities are.
+
+ if (print_version_flag) {
+ printf(_("%s version %s\n"), PACKAGE, VERSION);
+#ifdef HAVE_WIDE_CURSES
+ printf(_("Compiled with wide-curses.\n"));
+#endif
+ printf(_("\nTerminal encoding:\n%s, %s\n"),
+ terminal::is_utf8
+ ? "UTF-8"
+ : "8-bit",
+ terminal::is_fixed
+ ? _("cannot handle combining/wide characters")
+ : _("can handle combining/wide characters"));
+ if (terminal::force_iso88598)
+ printf(_("I'll treat this terminal as ISO-8859-8\n"));
+ printf(_("\nFiles I can use:\n"));
+ const char * files[] = {
+ USER_RC_FILE,
+ USER_TRANSTBL_FILE,
+ USER_REPRTBL_FILE,
+ USER_ALTKBDTBL_FILE,
+ USER_MANUAL_FILE,
+ USER_UNICODE_DATA_FILE,
+ USER_THEMES_DIR,
+ SYSTEM_RC_FILE,
+ SYSTEM_TRANSTBL_FILE,
+ SYSTEM_REPRTBL_FILE,
+ SYSTEM_ALTKBDTBL_FILE,
+ SYSTEM_MANUAL_FILE,
+ SYSTEM_UNICODE_DATA_FILE,
+ SYSTEM_THEMES_DIR,
+ NULL
+ };
+ int i = 0;
+ while (files[i])
+ printf("%s\n", get_cfg_filename(files[i++]));
+ exit(0);
+ }
+
+ if (!do_log2vis) {
+ terminal::init();
+ if (bw_flag)
+ terminal::is_color = false;
+ if (graphical_boxes != boxesAuto)
+ terminal::graphical_boxes = (graphical_boxes == boxesGraphical);
+
+ ThemeError theme_error;
+ bool rslt;
+ if (!theme || !terminal::is_color)
+ rslt = load_default_theme(theme_error);
+ else
+ rslt = load_theme(theme, theme_error);
+ if (!rslt) {
+ terminal::finish();
+ fatal(_("Can't load theme: %s\n"), theme_error.format().c_str());
+ }
+ } else {
+ terminal::is_fixed = false;
+ terminal::is_utf8 = true;
+ }
+
+ /*
+ * Step 3.
+ *
+ * Instantiate and initialize an Editor object.
+ * Load the file specified on the command line.
+ *
+ */
+
+ Editor bde;
+
+ bde.set_tab_width(tab_width);
+ bde.set_justification_column(justification_column);
+ bde.set_rfc2646_trailing_space(rfc2646_trailing_space_flag);
+ bde.set_wrap_type(wrap_type);
+ bde.set_dir_algo(dir_algo);
+ bde.set_scroll_step(scroll_step);
+ bde.set_maqaf_display(maqaf);
+ bde.set_rtl_nsm_display(rtl_nsm);
+ bde.set_backup_suffix(backup_suffix);
+ bde.set_smart_typing(smart_typing_flag);
+ bde.set_undo_size_limit(undo_size * 1024);
+ bde.set_key_for_key_undo(key_for_key_undo_flag);
+ bde.set_read_only(read_only_flag);
+ bde.set_speller_cmd(speller_cmd);
+ bde.set_speller_encoding(speller_encoding);
+ bde.adjust_speller_cmd();
+ bde.set_external_editor(external_editor);
+ bde.set_scrollbar_pos(scrollbar_pos);
+ bde.enable_bidi(enable_bidi);
+ bde.set_syntax_auto_detection(syntax_auto_detection);
+ bde.set_underline(underline);
+ bde.set_visual_cursor_movement(visual_cursor_movement);
+ if (default_file_encoding)
+ bde.set_default_encoding(default_file_encoding);
+ if (arabic_shaping_flag)
+ bde.toggle_arabic_shaping();
+ if (auto_indent_flag)
+ bde.toggle_auto_indent();
+ if (auto_justify_flag)
+ bde.toggle_auto_justify();
+ if (!show_formatting_flag)
+ bde.toggle_formatting_marks();
+ if (show_cursor_position_flag)
+ bde.toggle_cursor_position_report();
+#ifdef HAVE_CURS_SET
+ if (!do_log2vis && big_cursor_flag)
+ bde.set_big_cursor(true);
+#endif
+
+ // Load file
+
+ const char *filename = NULL;
+ int go_to_line = 0;
+ for (int i = optind; i < argc; i++) {
+ if (*argv[i] == '+')
+ go_to_line = atol(argv[i]);
+ else
+ filename = argv[i];
+ }
+
+ if (do_log2vis) {
+ bde.set_non_interactive_text_width(non_interactive_text_width);
+ if (filename) {
+ bde.load_file(filename, file_encoding);
+ if (bde.is_new()) {
+ // a crude method to print an error message
+ // when the file does not exist.
+ bde.insert_file(filename, file_encoding);
+ }
+ } else {
+ bde.load_file("-", file_encoding);
+ }
+ if (log2vis_output_encoding)
+ bde.set_encoding(log2vis_output_encoding);
+ } else {
+ if (filename)
+ bde.load_file(filename, file_encoding);
+ if (go_to_line > 0)
+ bde.go_to_line(go_to_line);
+ }
+
+ /*
+ * Step 4.
+ *
+ * Start the event pump.
+ *
+ */
+
+ if (!do_log2vis) {
+ bde.exec();
+ } else {
+ bde.log2vis(log2vis_options);
+ bde.save_file("-", bde.get_encoding());
+ }
+
+ if (!do_log2vis) {
+ terminal::finish();
+ }
+
+ return 0;
+}
+
diff --git a/menus.cc b/menus.cc
new file mode 100644
index 0000000..369343c
--- /dev/null
+++ b/menus.cc
@@ -0,0 +1,798 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#if HAVE_DIRENT_H
+# include <dirent.h> // for population the 'color scheme' sub menu.
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <algorithm> // sort
+
+#include "io.h" // get_cfg_filename
+#include "pathnames.h"
+#include "menus.h"
+
+#define STT_AUTOINDENT 1001
+#define STT_AUTOJUSTIFY 1002
+#define STT_ALTKBD 1003
+#define STT_SMRT 1004
+#define STT_ARABICSHAPING 1005
+#define STT_FORMATMARKS 1006
+#define STT_CURSORREPORT 1007
+#define STT_SPELLERLOADED 1008
+#define STT_READONLY 1009
+#define STT_BIGCURSOR 1010
+#define STT_GRAPHBOXES 1011
+#define STT_KEYFORKEYUNDO 1012
+#define STT_BIDI 1013
+#define STT_UNDERLINE 1014
+#define STT_SYNAUTO 1015
+
+#define STT_EOPUNIX 5001
+#define STT_EOPDOS 5002
+#define STT_EOPMAC 5003
+#define STT_EOPUNICODE 5004
+
+#define STT_RTLNSMASCII 5010
+#define STT_RTLNSMASIS 5011
+#define STT_RTLNSMHIDE 5012
+
+#define STT_MQFASCII 5020
+#define STT_MQFHIGHLIGHT 5021
+#define STT_MQFASIS 5022
+
+#define STT_WRAPWSPACE 5030
+#define STT_WRAPBREAK 5031
+#define STT_WRAPOFF 5032
+
+#define STT_DIRALGOUNICODE 5040
+#define STT_DIRALGOCTXSTRNG 5041
+#define STT_DIRALGOCTXRTL 5042
+#define STT_DIRALGOFLTR 5043
+#define STT_DIRALGOFRTL 5044
+
+#define STT_SCRLBRNONE 5060
+#define STT_SCRLBRLEFT 5061
+#define STT_SCRLBRRIGHT 5062
+
+#define STT_SYNNONE 5070
+#define STT_SYNHTML 5071
+#define STT_SYNEMAIL 5072
+
+#define STT_CRSVIS 5080
+#define STT_CRSLOG 5081
+
+#define STT_THEME_START 6000
+#define STT_THEME_END 6999
+
+#define CMD_CHR 100
+#define CMD_CRSVIS 101
+#define CMD_CRSLOG 102
+#define CMD_SETTHEME 103
+#define CMD_SETFLAG 104
+#define CMD_ENC 105
+#define CMD_INTERACTIVEENC 106
+
+#define CMD_HELP 120
+
+#define FLAG_FILE_ENCODING 1
+#define FLAG_DEFAULT_ENCODING 2
+
+#define HELP_ITEM N_("Get a detailed explanation for the above")
+
+#define HELP_TOPIC_ALGO_STR "\xD7\x90\xD7\x9C\xD7\x92\xD7\x95\xD7\xA8\xD7\x99\xD7\xAA\xD7\x9D\x20\xD7\x9C\xD7\xA7\xD7\x91\xD7\x99\xD7\xA2\xD7"
+#define HELP_TOPIC_POINTS_STR "\xD7\xA0\xD7\x99\xD7\xA7\xD7\x95\xD7\x93\x20\xD7\xA2\xD7\x91\xD7\xA8\xD7\x99"
+#define HELP_TOPIC_MAQAF_STR "\xD7\x9E\xD7\xA7\xD7\xA3"
+#define HELP_TOPIC_EOL_STR "\xD7\xA1\xD7\x95\xD7\xA4\xD7\x99\x20\xD7\xA9\xD7\x95\xD7\xA8\xD7\x95\xD7\xAA"
+#define HELP_TOPIC_CRSLV_STR "\xD7\xAA\xD7\xA0\xD7\x95\xD7\xA2\xD7\xAA\x20\xD7\xA1\xD7\x9E\xD7\x9F"
+#define HELP_TOPIC_SYNHLT_STR "\xD7\xA6\xD7\x91\xD7\x99\xD7\xA2\xD7\x94\x20\xD7\xAA\xD7\x97\xD7\x91\xD7\x99\xD7\xA8\xD7\x99\xD7\xAA"
+#define HELP_TOPIC_WRAP_STR "\xD7\xA7\xD7\x99\xD7\xA4\xD7\x95\xD7\x9C\x20\xD7\xA4\xD7\xA1\xD7\xA7\xD7\x90\xD7\x95\xD7\xAA"
+#define HELP_TOPIC_COLORS_STR "\xD7\xA2\xD7\xA8\xD7\x9B\xD7\x95\xD7\xAA\x20\xD7\xA6\xD7\x91\xD7\xA2\xD7\x99\xD7\x9D"
+#define HELP_TOPIC_ENCODING_STR "\xD7\xA7\xD7\x99\xD7\x93\xD7\x95\xD7\x93\x20\xD7\x94\xD7\xA7\xD7\x91\xD7\xA6\xD7\x99\xD7\x9D"
+#define HELP_TOPIC_EXTED_STR "\xD7\x94\xD7\xA4\xD7\xA2\xD7\x9C\xD7\xAA\x20\xD7\xA2\xD7\x95\xD7\xA8\xD7\x9A\x20\xD7\x97\xD7\x99\xD7\xA6\xD7\x95\xD7\xA0\xD7\x99"
+
+PulldownMenu FileMenu = {
+ { "load_file", N_("~Open...") },
+ { "insert_file", N_("~Insert file...") },
+ { "-----------" },
+ { "save_file", N_("~Save") },
+ { "save_file_as", N_("Save ~As...") },
+ { "write_selection_to_file", N_("~Write selection to...") },
+ { "-----------" },
+ { "external_edit_prompt", N_("Launch external ~editor...") },
+ { "external_edit_no_prompt", N_("Launch external editor") },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_EXTED_STR },
+ { "-----------" },
+ { "change_directory", N_("Ch~dir...") },
+ { "quit", N_("~Quit") },
+ { NULL }
+};
+
+PulldownMenu MovementMenu = {
+ { "move_beginning_of_buffer", N_("Move to the ~beginning of the buffer") },
+ { "move_end_of_buffer", N_("Move to the ~end of the buffer") },
+ { "go_to_line", N_("Go to a specific ~line") },
+ { "move_last_modification", N_("Jump to the last ~modification point") },
+ { "-----------" },
+ { "key_left", N_("Move a character left") },
+ { "key_right", N_("Move a character right") },
+ { "move_previous_line", N_("Move to the previous line") },
+ { "move_next_line", N_("Move to the next line") },
+ { "move_backward_page", N_("Move to the previous page") },
+ { "move_forward_page", N_("Move to the next page") },
+ { "move_backward_char", N_("Move back a character") },
+ { "move_forward_char", N_("Move forward a character") },
+ //{ "move_beginning_of_line", N_("Move to the beginning of the current line") },
+ { "key_home", N_("Move to the beginning of the current line") },
+ { "move_end_of_line", N_("Move to the end of the current line") },
+ { NULL }
+};
+
+PulldownMenu DeletionMenu = {
+ { "delete_backward_char", N_("Delete the previous character") },
+ { "delete_forward_char", N_("Delete the character the cursor is on") },
+ { "delete_backward_word", N_("Delete to the start of the current or previous word") },
+ { "delete_forward_word", N_("Delete to the end of the current or next word") },
+ { "delete_paragraph", N_("Delete the current paragraph") },
+ { "cut_end_of_paragraph", N_("Cut [to] end of paragraph") },
+ { NULL }
+};
+
+PulldownMenu EditMenu = {
+ { "undo", N_("~Undo") },
+ { "redo", N_("~Redo") },
+ { "toggle_key_for_key_undo", N_("Toggle ~key-for-key undo"), STT_KEYFORKEYUNDO },
+ { "-----------" },
+ { "copy", N_("~Copy") },
+ { "cut", N_("Cu~t") },
+ { "paste",N_("~Paste") },
+ { "toggle_primary_mark", N_("Start/cancel selection") },
+ { "-----------" },
+ { "", N_("~Movement"), 0, MovementMenu },
+ { "", N_("~Deletion"), 0, DeletionMenu },
+ { "toggle_read_only", N_("Toggle read-only status of buffer"), STT_READONLY },
+ { "-----------" },
+ { "search_forward", N_("~Search...") },
+ { "search_forward_next", N_("Search ~next") },
+ { "-----------" },
+ { "toggle_auto_justify", N_("Toggle auto-~justify"), STT_AUTOJUSTIFY },
+ { "justify", N_("Justify the current or next paragraph") },
+ { "change_justification_column", N_("Change justify column...") },
+ { "-----------" },
+ { "toggle_auto_indent", N_("Toggle auto-~indent"), STT_AUTOINDENT },
+ { "change_tab_width", N_("Change the TAB character size...") },
+ { NULL }
+};
+
+PulldownMenu EolMenu = {
+ { "set_eops_unix", N_("~Unix (LF)"), STT_EOPUNIX },
+ { "set_eops_dos", N_("~DOS/Windows (CR,LF)"), STT_EOPDOS },
+ { "set_eops_mac", N_("~Macintosh (CR)"), STT_EOPMAC },
+ { "set_eops_unicode", N_("U~nicode PS (U+2029)"), STT_EOPUNICODE },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_EOL_STR },
+ { NULL }
+};
+
+#define ICONVREQ N_("(requires iconv)")
+
+PulldownMenu CyrillicEncodingsMenu = {
+ { "xxx", N_("(Cyrillic) ~Windows-1251"), 0, 0, CMD_ENC, 0, "CP1251", ICONVREQ },
+ { "xxx", N_("(Cyrillic) ~ISO-8859-5"), 0, 0, CMD_ENC, 0, "ISO-8859-5", ICONVREQ },
+ { "xxx", N_("(Cyrillic) KOI8-~R"), 0, 0, CMD_ENC, 0, "KOI8-R", ICONVREQ },
+ { "xxx", N_("(Cyrillic) KOI8-~U"), 0, 0, CMD_ENC, 0, "KOI8-U", ICONVREQ },
+ { "xxx", N_("(Cyrillic) IBM-866"), 0, 0, CMD_ENC, 0, "CP866", ICONVREQ },
+ { "xxx", N_("(Cyrillic) IBM-855"), 0, 0, CMD_ENC, 0, "CP855", ICONVREQ },
+ { "xxx", N_("(Cyrillic) ISO-IR-111"), 0, 0, CMD_ENC, 0, "ISO-IR-111", ICONVREQ },
+ { "xxx", N_("(Cyrillic) ~MacCyrillic"), 0, 0, CMD_ENC, 0, "MACCYRILLIC", ICONVREQ },
+ { NULL }
+};
+
+PulldownMenu UnicodeEncodingsMenu = {
+ { "xxx", N_("UTF-1~6"), 0, 0, CMD_ENC, 0, "UTF-16", ICONVREQ },
+ { "xxx", N_("UTF-16 Big Endian"), 0, 0, CMD_ENC, 0, "UTF-16BE", ICONVREQ },
+ { "xxx", N_("UTF-16 Little Endian"), 0, 0, CMD_ENC, 0, "UTF-16LE", ICONVREQ },
+ { "xxx", N_("UTF-~32"), 0, 0, CMD_ENC, 0, "UTF-32", ICONVREQ },
+ { "xxx", N_("UTF-32 Big Endian"), 0, 0, CMD_ENC, 0, "UTF-32BE", ICONVREQ },
+ { "xxx", N_("UTF-32 Little Endian"), 0, 0, CMD_ENC, 0, "UTF-32LE", ICONVREQ },
+ { "xxx", N_("UTF-~7"), 0, 0, CMD_ENC, 0, "UTF-7", ICONVREQ },
+ { "-----------" },
+ { "xxx", N_("UCS-2"), 0, 0, CMD_ENC, 0, "UCS-2", ICONVREQ },
+ { "xxx", N_("UCS-2 Big Endian"), 0, 0, CMD_ENC, 0, "UCS-2BE", ICONVREQ },
+ { "xxx", N_("UCS-2 Little Endian"), 0, 0, CMD_ENC, 0, "UCS-2BE", ICONVREQ },
+ { "xxx", N_("UCS-4"), 0, 0, CMD_ENC, 0, "UCS-4", ICONVREQ },
+ { "xxx", N_("UCS-4 Big Endian"), 0, 0, CMD_ENC, 0, "UCS-4BE", ICONVREQ },
+ { "xxx", N_("UCS-4 Little Endian"), 0, 0, CMD_ENC, 0, "UCS-4LE", ICONVREQ },
+ { NULL }
+};
+
+PulldownMenu EncodingsMenu = {
+ { "xxx", N_("~UTF-8"), 0, 0, CMD_ENC, 0, "UTF-8" },
+ { "-----------" },
+ { "xxx", N_("(Hebrew) ~ISO-8859-8"), 0, 0, CMD_ENC, 0, "ISO-8859-8" },
+ { "xxx", N_("(Hebrew) ~Windows-1255"), 0, 0, CMD_ENC, 0, "CP1255", ICONVREQ },
+ { "xxx", N_("(Hebrew) MacHebrew"), 0, 0, CMD_ENC, 0, "MACHEBREW", ICONVREQ },
+ { "xxx", N_("(Hebrew) IBM-862 (~Dos)"), 0, 0, CMD_ENC, 0, "CP862", ICONVREQ },
+ { "-----------" },
+ { "xxx", N_("(Arabic) ISO-8859-6"), 0, 0, CMD_ENC, 0, "ISO-8859-6", ICONVREQ },
+ { "xxx", N_("(~Arabic) Windows-1256"), 0, 0, CMD_ENC, 0, "CP1256", ICONVREQ },
+ { "xxx", N_("(Arabic) MacArabic"), 0, 0, CMD_ENC, 0, "MACARABIC", ICONVREQ },
+ { "xxx", N_("(Arabic) IBM-864 (Dos)"), 0, 0, CMD_ENC, 0, "CP864", ICONVREQ },
+ { "xxx", N_("(Farsi) MacFarsi"), 0, 0, CMD_ENC, 0, "MACFARSI", ICONVREQ },
+ { "xxx", N_("~Cyrillic"), 0, CyrillicEncodingsMenu },
+ { "xxx", N_("~More Unicodes"), 0, UnicodeEncodingsMenu },
+ { "-----------" },
+ { "xxx", N_("Other..."), 0, 0, CMD_INTERACTIVEENC },
+ { NULL }
+};
+
+PulldownMenu CantillationsCharMenu = {
+ { "xxx", N_("ETNAHTA (U+0591)"), 0, 0, CMD_CHR, 0x0591 },
+ { "xxx", N_("SEGOL (U+0592)"), 0, 0, CMD_CHR, 0x0592 },
+ { "xxx", N_("SHALSHELET (U+0593)"), 0, 0, CMD_CHR, 0x0593 },
+ { "xxx", N_("ZAQEF QATAN (U+0594)"), 0, 0, CMD_CHR, 0x0594 },
+ { "xxx", N_("ZAQEF GADOL (U+0595)"), 0, 0, CMD_CHR, 0x0595 },
+ { "xxx", N_("TIPEHA (U+0596)"), 0, 0, CMD_CHR, 0x0596 },
+ { "xxx", N_("REVIA (U+0597)"), 0, 0, CMD_CHR, 0x0597 },
+ { "xxx", N_("ZARQA (U+0598)"), 0, 0, CMD_CHR, 0x0598 },
+ { "xxx", N_("PASHTA (U+0599)"), 0, 0, CMD_CHR, 0x0599 },
+ { "xxx", N_("YETIV (U+059A)"), 0, 0, CMD_CHR, 0x059A },
+ { "xxx", N_("TEVIR (U+059B)"), 0, 0, CMD_CHR, 0x059B },
+ { "xxx", N_("GERESH (U+059C)"), 0, 0, CMD_CHR, 0x059C },
+ { "xxx", N_("GERESH MUQDAM (U+059D)"), 0, 0, CMD_CHR, 0x059D },
+ { "xxx", N_("GERSHAYIM (U+059E)"), 0, 0, CMD_CHR, 0x059E },
+ { "xxx", N_("QARNEY PARA (U+059F)"), 0, 0, CMD_CHR, 0x059F },
+ { "xxx", N_("TELISHA GEDOLA (U+05A0)"), 0, 0, CMD_CHR, 0x05A0 },
+ { "xxx", N_("PAZER (U+05A1)"), 0, 0, CMD_CHR, 0x05A1 },
+ { "xxx", N_("MUNAH (U+05A3)"), 0, 0, CMD_CHR, 0x05A3 },
+ { "xxx", N_("MAHAPAKH (U+05A4)"), 0, 0, CMD_CHR, 0x05A4 },
+ { "xxx", N_("MERKHA (U+05A5)"), 0, 0, CMD_CHR, 0x05A5 },
+ { "xxx", N_("MERKHA KEFULA (U+05A6)"), 0, 0, CMD_CHR, 0x05A6 },
+ { "xxx", N_("DARGA (U+05A7)"), 0, 0, CMD_CHR, 0x05A7 },
+ { "xxx", N_("QADMA (U+05A8)"), 0, 0, CMD_CHR, 0x05A8 },
+ { "xxx", N_("TELISHA QETANA (U+05A9)"), 0, 0, CMD_CHR, 0x05A9 },
+ { "xxx", N_("YERAH BEN YOMO (U+05AA)"), 0, 0, CMD_CHR, 0x05AA },
+ { "xxx", N_("OLE (U+05AB)"), 0, 0, CMD_CHR, 0x05AB },
+ { "xxx", N_("ILUY (U+05AC)"), 0, 0, CMD_CHR, 0x05AC },
+ { "xxx", N_("DEHI (U+05AD)"), 0, 0, CMD_CHR, 0x05AD },
+ { "xxx", N_("ZINOR (U+05AE)"), 0, 0, CMD_CHR, 0x05AE },
+ { "xxx", N_("MASORA CIRCLE (U+05AF)"), 0, 0, CMD_CHR, 0x05AF },
+ { "-----------" },
+ { "xxx", N_("METEG (U+05BD)"), 0, 0, CMD_CHR, UNI_HEB_METEG },
+ { "xxx", N_("PASEQ (U+05C0)"), 0, 0, CMD_CHR, UNI_HEB_PASEQ },
+ { "xxx", N_("SOF PASUQ (U+05C3)"), 0, 0, CMD_CHR, UNI_HEB_SOF_PASUQ },
+ { NULL }
+};
+
+PulldownMenu ArabicCharMenu = {
+ { "xxx", N_("~FATHATAN (U+064B)"), 0, 0, CMD_CHR, 0x064B },
+ { "xxx", N_("DAMMATAN (U+064C)"), 0, 0, CMD_CHR, 0x064C },
+ { "xxx", N_("~KASRATAN (U+064D)"), 0, 0, CMD_CHR, 0x064D },
+ { "xxx", N_("FATHA (U+064E)"), 0, 0, CMD_CHR, 0x064E },
+ { "xxx", N_("~DAMMA (U+064F)"), 0, 0, CMD_CHR, 0x064F },
+ { "xxx", N_("KASRA (U+0650)"), 0, 0, CMD_CHR, 0x0650 },
+ { "xxx", N_("~SHADDA (U+0651)"), 0, 0, CMD_CHR, 0x0651 },
+ { "xxx", N_("SUKUN (U+0652)"), 0, 0, CMD_CHR, 0x0652 },
+ { "xxx", N_("SUPERSCRIPT ALEF (U+0670)"), 0, 0, CMD_CHR, 0x0670 },
+ { "-----------" },
+ { "xxx", N_("ZWJ, ZERO WIDTH JOINER (U+200D)"), 0, 0, CMD_CHR, 0x200D },
+ { "xxx", N_("ZWNJ, ZERO WIDTH NON-JOINER (U+200C)"), 0, 0, CMD_CHR, 0x200C },
+ { NULL }
+};
+
+PulldownMenu InsertCharMenu = {
+ { "insert_maqaf", N_("Hebrew ~maqaf (U+05BE)") },
+ { "-----------" },
+ { "xxx", N_("~LRM, LEFT-TO-RIGHT MARK (U+200E)"), 0, 0, CMD_CHR, UNI_LRM },
+ { "xxx", N_("~RLM, RIGHT-TO-LEFT MARK (U+200F)"), 0, 0, CMD_CHR, UNI_RLM },
+ { "xxx", N_("LRE, LEFT-TO-RIGHT EMBEDDING (U+202A)"), 0, 0, CMD_CHR, UNI_LRE },
+ { "xxx", N_("RLE, RIGHT-TO-LEFT EMBEDDING (U+202B)"), 0, 0, CMD_CHR, UNI_RLE },
+ { "xxx", N_("PDF, POP DIRECTIONAL FORMATTING (U+202C)"), 0, 0, CMD_CHR, UNI_PDF },
+ { "xxx", N_("LRO, LEFT-TO-RIGHT OVERRIDE (U+202D)"), 0, 0, CMD_CHR, UNI_LRO },
+ { "xxx", N_("RLO, RIGHT-TO-LEFT OVERRIDE (U+202E)"), 0, 0, CMD_CHR, UNI_RLO },
+ { "-----------" },
+ { "xxx", N_("~SHEVA (U+05B0)"), 0, 0, CMD_CHR, UNI_HEB_SHEVA },
+ { "xxx", N_("HATAF SEGOL (U+05B~1)"), 0, 0, CMD_CHR, UNI_HEB_HATAF_SEGOL },
+ { "xxx", N_("HATAF PATAH (U+05B~2)"), 0, 0, CMD_CHR, UNI_HEB_HATAF_PATAH },
+ { "xxx", N_("HATAF QAMATS (U+05B~3)"), 0, 0, CMD_CHR, UNI_HEB_HATAF_QAMATS },
+ { "xxx", N_("~HIRIQ (U+05B4)"), 0, 0, CMD_CHR, UNI_HEB_HIRIQ },
+ { "xxx", N_("TS~ERE (U+05B5)"), 0, 0, CMD_CHR, UNI_HEB_TSERE },
+ { "xxx", N_("SE~GOL (U+05B6)"), 0, 0, CMD_CHR, UNI_HEB_SEGOL },
+ { "xxx", N_("~PATAH (U+05B7)"), 0, 0, CMD_CHR, UNI_HEB_PATAH },
+ { "xxx", N_("~QAMATS (U+05B8)"), 0, 0, CMD_CHR, UNI_HEB_QAMATS },
+ { "xxx", N_("H~OLAM (U+05B9)"), 0, 0, CMD_CHR, UNI_HEB_HOLAM },
+ { "xxx", N_("Q~UBUTS (U+05BB)"), 0, 0, CMD_CHR, UNI_HEB_QUBUTS },
+ { "xxx", N_("~DAGESH OR MAPIQ (U+05BC)"), 0, 0, CMD_CHR, UNI_HEB_DAGESH_OR_MAPIQ },
+ { "xxx", N_("~RAFE (U+05BF)"), 0, 0, CMD_CHR, UNI_HEB_RAFE },
+ { "xxx", N_("SHIN DOT (U+05C1)"), 0, 0, CMD_CHR, UNI_HEB_SHIN_DOT },
+ { "xxx", N_("SIN DOT (U+05C2)"), 0, 0, CMD_CHR, UNI_HEB_SIN_DOT },
+ { "-----------" },
+ { "xxx", N_("GERESH (U+05F3)"), 0, 0, CMD_CHR, UNI_HEB_GERESH },
+ { "xxx", N_("GERSHAYIM (U+05F4)"), 0, 0, CMD_CHR, UNI_HEB_GERSHAYIM },
+ { "xxx", N_("NO-BREAK SPACE (U+00A0)"), 0, 0, CMD_CHR, 0x00A0 },
+ { "xxx", N_("ZERO WIDTH SPACE(U+200B)"), 0, 0, CMD_CHR, 0x200B },
+ { "-----------" },
+ { "xxx", N_("~Cantillations"), 0, CantillationsCharMenu },
+ { "xxx", N_("~Arabic"), 0, ArabicCharMenu },
+};
+
+PulldownMenu CharactersUtilsMenu = {
+ { "show_character_code", N_("Print ~Unicode and UTF-8 value") },
+ { "show_character_info", N_("Print the corresponding Unicode~Data.txt line") },
+ { "xxx", N_("~Insert a Unicode character from the list"), 0, InsertCharMenu },
+ { "insert_unicode_char", N_("Insert a Unicode character using its ~code") },
+ { "-----------" },
+ { "xxx", N_("Set the ~encoding used for saving this file"), 0, EncodingsMenu, CMD_SETFLAG, FLAG_FILE_ENCODING },
+ { "xxx", N_("Set the de~fault encoding"), 0, EncodingsMenu, CMD_SETFLAG, FLAG_DEFAULT_ENCODING, 0,
+ N_("The \"default encoding\" is used when loading files and when creating new files.") },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_ENCODING_STR },
+ { "-----------" },
+ { "toggle_alt_kbd", N_("Toogle ~Hebrew keyboard emulation"), STT_ALTKBD },
+ { "toggle_smart_typing", N_("Toggle ~smart-typing mode"), STT_SMRT },
+ { "set_translate_next_char", N_("~Translate next character") },
+ { "-----------" },
+ { "toggle_eops", N_("Change end-of-~line type"), 0, EolMenu },
+ { NULL }
+};
+
+PulldownMenu DirAlgoMenu = {
+ { "set_dir_algo_unicode", N_("~Unicode's TR #9"), STT_DIRALGOUNICODE },
+ { "set_dir_algo_context_strong", N_("Contextual-~strong"), STT_DIRALGOCTXSTRNG },
+ { "set_dir_algo_context_rtl", N_("~Contextual-rtl"), STT_DIRALGOCTXRTL },
+ { "set_dir_algo_force_ltr", N_("Force ~LTR"), STT_DIRALGOFLTR },
+ { "set_dir_algo_force_rtl", N_("Force ~RTL"), STT_DIRALGOFRTL },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_ALGO_STR },
+ { NULL }
+};
+
+PulldownMenu CursorMovementMenu = {
+ { "toggle_visual_cursor_movement", N_("~Logical"), STT_CRSLOG, 0, CMD_CRSLOG, 0,
+ N_("Logical movement: cursor follows the buffer order") },
+ { "toggle_visual_cursor_movement", N_("~Visual"), STT_CRSVIS, 0, CMD_CRSVIS, 0,
+ N_("Visual movement: cursor follows what you see on screen") },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_CRSLV_STR },
+ { NULL }
+};
+
+PulldownMenu BiDiMenu = {
+ { "toggle_bidi", N_("Toggle the ~BiDi algorithm"), STT_BIDI },
+ { "toggle_dir_algo", N_("Select base-directionality ~algorithm"), 0, DirAlgoMenu },
+ { "toggle_visual_cursor_movement", N_("~Cursor movement"), 0, CursorMovementMenu },
+ { NULL }
+};
+
+PulldownMenu SpellerMenu = {
+ { "spell_check_all", N_("Spell check all ~document") },
+ { "spell_check_forward", N_("Spell check ~from cursor onward") },
+ { "spell_check_word", N_("Spell check ~word under cursor") },
+ { "-----------" },
+ { "load_unload_speller", N_("Explicitly ~load/unload the speller process..."), STT_SPELLERLOADED },
+ { NULL }
+};
+
+PulldownMenu WrapMenu = {
+ { "set_wrap_type_at_white_space",
+ N_("Wrap lines, do not break ~words"), STT_WRAPWSPACE },
+ { "set_wrap_type_anywhere",
+ N_("Wrap lines, ~break words"), STT_WRAPBREAK },
+ { "set_wrap_type_off",
+ N_("Do ~not wrap lines"), STT_WRAPOFF },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_WRAP_STR },
+ { NULL }
+};
+
+PulldownMenu rtlnsmMenu = {
+ { "set_rtl_nsm_transliterated",
+ N_("Display Hebrew/Arabic points as highlighted ~ASCII characters"), STT_RTLNSMASCII },
+ { "set_rtl_nsm_asis",
+ N_("Display Hebrew/Arabic points as-~is (for capable terminals)"), STT_RTLNSMASIS },
+ { "set_rtl_nsm_off",
+ N_("~Hide Hebrew/Arabic points"), STT_RTLNSMHIDE },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_POINTS_STR },
+ { NULL }
+};
+
+PulldownMenu MaqafMenu = {
+ { "set_maqaf_display_transliterated",
+ N_("Display the maqaf as ~ASCII dash"), STT_MQFASCII },
+ { "set_maqaf_display_asis",
+ N_("Display the maqaf as-~is (for capable terminals)"),
+ STT_MQFASIS },
+ { "set_maqaf_display_highlighted",
+ N_("~Highlight the maqaf"), STT_MQFHIGHLIGHT },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_MAQAF_STR },
+ { NULL }
+};
+
+PulldownMenu ScrollbarMenu = {
+ { "menu_set_scrollbar_none", N_("~Off"), STT_SCRLBRNONE },
+ { "menu_set_scrollbar_left", N_("~Left"), STT_SCRLBRLEFT },
+ { "menu_set_scrollbar_right", N_("~Right"), STT_SCRLBRRIGHT },
+ { NULL }
+};
+
+PulldownMenu HighlightMenu = {
+ { "toggle_syntax_auto_detection", N_("Syntax ~Auto-Detection"), STT_SYNAUTO },
+ { "-----------" },
+ { "menu_set_syn_hlt_none", N_("~None"), STT_SYNNONE },
+ { "menu_set_syn_hlt_html", N_("~HTML"), STT_SYNHTML },
+ { "menu_set_syn_hlt_email", N_("~Email"), STT_SYNEMAIL },
+ { "-----------" },
+ { "toggle_underline", N_("Highlight *~text* and _text_"), STT_UNDERLINE },
+ { "-----------" },
+ { "xxx", HELP_ITEM, 0, 0, CMD_HELP, 0, HELP_TOPIC_SYNHLT_STR },
+ { NULL }
+};
+
+#define MAX_COLOR_SCHEMES 100
+MenuItem ColorScheme[MAX_COLOR_SCHEMES] = {
+ { "set_default_theme", N_("~Default (transparent)"), STT_THEME_START, 0, 0, 0, "default.thm" },
+ { "-----------" },
+};
+
+PulldownMenu DisplayMenu = {
+ { "toggle_formatting_marks", N_("Toggle display of ~formatting marks"), STT_FORMATMARKS },
+ { "toggle_arabic_shaping", N_("Toggle ~Arabic shaping"), STT_ARABICSHAPING },
+ { "xxx_toggle_rtl_nsm", N_("Change display of Hebrew/Arabic ~points"), 0, rtlnsmMenu },
+ { "xxx_toggle_maqaf", N_("Change display of the Hebrew ~maqaf"), 0, MaqafMenu },
+ { "-----------" },
+ { "toggle_wrap", N_("Change ~wrap style"), 0, WrapMenu },
+ { "xxx", N_("~Scrollbar"), 0, ScrollbarMenu },
+ { "toggle_cursor_position_report", N_("Toggle display of cursor p~osition"), STT_CURSORREPORT },
+ { "change_scroll_step", N_("Change the scroll step...") },
+ { "-----------" },
+ { "xxx", N_("~Color scheme"), 0, ColorScheme },
+ { "-----------" },
+ { "xxx", N_("Syntax ~Highlighting"), 0, HighlightMenu },
+ { "-----------" },
+ { "refresh_and_center", N_("~Repaint the screen and center the line") },
+ { "toggle_graphical_boxes", N_("Toggle use of ~graphical characters for frames"), STT_GRAPHBOXES },
+ { "toggle_big_cursor", N_("Toggle ~big cursor"), STT_BIGCURSOR },
+ { NULL }
+};
+
+PulldownMenu HelpMenu = {
+ { "help", N_("User ~Manual") },
+ { "describe_key", N_("~Describe key...") },
+ { NULL }
+};
+
+MenubarMenu mainMenu = {
+ { "File", FileMenu },
+ { "Edit", EditMenu },
+ { "Characters", CharactersUtilsMenu },
+ { "Display", DisplayMenu },
+ { "BiDi", BiDiMenu },
+ { "Speller", SpellerMenu },
+ { "Help", HelpMenu },
+ { NULL }
+};
+
+////////////////////////////// MMPopupMenu /////////////////////////////////
+
+class MMPopupMenu : public PopupMenu {
+ Editor *editor;
+ EditBox *editbox;
+public:
+ MMPopupMenu(Editor *aEditor, EditBox *aEditbox,
+ PopupMenu *aParent, PulldownMenu aMnu);
+protected:
+ virtual void show_hint(const char *hint);
+ virtual void clear_other_popups();
+ virtual Dispatcher *get_primary_target();
+ virtual Dispatcher *get_secondary_target();
+ virtual PopupMenu *create_popupmenu(PopupMenu *aParent, PulldownMenu mnu);
+ virtual bool get_item_state(int id);
+ virtual void do_command(unsigned long parameter1, unsigned long parameter2,
+ const char *parameter3);
+};
+
+MMPopupMenu::MMPopupMenu(Editor *aEditor, EditBox *aEditbox,
+ PopupMenu *aParent, PulldownMenu aMnu)
+ : PopupMenu(aParent, NULL)
+{
+ editor = aEditor;
+ editbox = aEditbox;
+ init(aMnu);
+}
+
+void MMPopupMenu::show_hint(const char *hint)
+{
+ editor->show_hint(hint);
+}
+
+void MMPopupMenu::clear_other_popups()
+{
+ editor->refresh(true);
+}
+
+Dispatcher *MMPopupMenu::get_primary_target()
+{
+ return editor;
+}
+
+Dispatcher *MMPopupMenu::get_secondary_target()
+{
+ return editbox;
+}
+
+PopupMenu *MMPopupMenu::create_popupmenu(PopupMenu *aParent, PulldownMenu mnu)
+{
+ return new MMPopupMenu(editor, editbox, aParent, mnu);
+}
+
+bool MMPopupMenu::get_item_state(int id)
+{
+ switch (id) {
+ case STT_AUTOINDENT: return editbox->is_auto_indent();
+ case STT_AUTOJUSTIFY: return editbox->is_auto_justify();
+ case STT_ALTKBD: return editbox->get_alt_kbd();
+ case STT_SMRT: return editbox->is_smart_typing();
+ case STT_ARABICSHAPING: return terminal::do_arabic_shaping;
+ case STT_FORMATMARKS: return editbox->has_formatting_marks();
+ case STT_CURSORREPORT: return editor->is_cursor_position_report();
+ case STT_READONLY: return editbox->is_read_only();
+ case STT_BIGCURSOR: return editor->is_big_cursor();
+ case STT_GRAPHBOXES: return terminal::graphical_boxes;
+ case STT_KEYFORKEYUNDO: return editbox->is_key_for_key_undo();
+ case STT_BIDI: return editbox->is_bidi_enabled();
+
+ case STT_EOPUNIX: return editbox->get_dominant_eop() == eopUnix;
+ case STT_EOPDOS: return editbox->get_dominant_eop() == eopDOS;
+ case STT_EOPMAC: return editbox->get_dominant_eop() == eopMac;
+ case STT_EOPUNICODE: return editbox->get_dominant_eop() == eopUnicode;
+
+ case STT_RTLNSMASCII: return editbox->get_rtl_nsm_display() == EditBox::rtlnsmTransliterated;
+ case STT_RTLNSMASIS: return editbox->get_rtl_nsm_display() == EditBox::rtlnsmAsis;
+ case STT_RTLNSMHIDE: return editbox->get_rtl_nsm_display() == EditBox::rtlnsmOff;
+
+ case STT_MQFASCII: return editbox->get_maqaf_display() == EditBox::mqfTransliterated;
+ case STT_MQFHIGHLIGHT: return editbox->get_maqaf_display() == EditBox::mqfHighlighted;
+ case STT_MQFASIS: return editbox->get_maqaf_display() == EditBox::mqfAsis;
+
+ case STT_WRAPOFF: return editbox->get_wrap_type() == EditBox::wrpOff;
+ case STT_WRAPBREAK: return editbox->get_wrap_type() == EditBox::wrpAnywhere;
+ case STT_WRAPWSPACE: return editbox->get_wrap_type() == EditBox::wrpAtWhiteSpace;
+
+ case STT_DIRALGOUNICODE: return editbox->get_dir_algo() == algoUnicode;
+ case STT_DIRALGOCTXSTRNG: return editbox->get_dir_algo() == algoContextStrong;
+ case STT_DIRALGOCTXRTL: return editbox->get_dir_algo() == algoContextRTL;
+ case STT_DIRALGOFLTR: return editbox->get_dir_algo() == algoForceLTR;
+ case STT_DIRALGOFRTL: return editbox->get_dir_algo() == algoForceRTL;
+
+ case STT_SPELLERLOADED: return editor->is_speller_loaded();
+
+ case STT_SCRLBRNONE: return editor->get_scrollbar_pos() == Editor::scrlbrNone;
+ case STT_SCRLBRLEFT: return editor->get_scrollbar_pos() == Editor::scrlbrLeft;
+ case STT_SCRLBRRIGHT: return editor->get_scrollbar_pos() == Editor::scrlbrRight;
+
+ case STT_SYNNONE: return editbox->get_syn_hlt() == EditBox::synhltOff;
+ case STT_SYNHTML: return editbox->get_syn_hlt() == EditBox::synhltHTML;
+ case STT_SYNEMAIL: return editbox->get_syn_hlt() == EditBox::synhltEmail;
+
+ case STT_UNDERLINE: return editbox->get_underline();
+
+ case STT_SYNAUTO: return editor->get_syntax_auto_detection();
+
+ case STT_CRSVIS: return editbox->get_visual_cursor_movement();
+ case STT_CRSLOG: return !editbox->get_visual_cursor_movement();
+
+ default:
+ if (id >= STT_THEME_START && id <= STT_THEME_END) {
+ int i = 0;
+ while (ColorScheme[i].action) {
+ if (ColorScheme[i].state_id == id)
+ return STREQ(ColorScheme[i].command_parameter3, editor->get_theme_name());
+ i++;
+ }
+ return false;
+ }
+ else {
+ return false;
+ }
+ }
+}
+
+void MMPopupMenu::do_command(unsigned long parameter1, unsigned long parameter2,
+ const char *parameter3)
+{
+ static int flag;
+ switch (parameter1) {
+ case CMD_SETTHEME:
+ editor->set_theme(parameter3);
+ break;
+ case CMD_CHR:
+ editbox->insert_char((unichar)parameter2);
+ break;
+ case CMD_CRSVIS:
+ editbox->set_visual_cursor_movement(true);
+ break;
+ case CMD_CRSLOG:
+ editbox->set_visual_cursor_movement(false);
+ break;
+ case CMD_HELP:
+ editor->show_help_topic(parameter3);
+ break;
+ case CMD_ENC:
+ if (flag == FLAG_DEFAULT_ENCODING)
+ editor->set_default_encoding(parameter3);
+ else
+ editor->set_encoding(parameter3);
+ break;
+ case CMD_INTERACTIVEENC:
+ editor->menu_set_encoding(flag == FLAG_DEFAULT_ENCODING);
+ break;
+ case CMD_SETFLAG:
+ flag = parameter2;
+ break;
+ }
+}
+
+/////////////////////////////// MMMenubar //////////////////////////////////
+
+class MMMenubar : public Menubar {
+ Editor *editor;
+ EditBox *editbox;
+ void populate_color_scheme_menu();
+public:
+ MMMenubar(Editor *, EditBox *);
+ virtual void refresh_screen();
+ virtual PopupMenu *create_popupmenu(PulldownMenu mnu);
+};
+
+MMMenubar::MMMenubar(Editor *aEditor, EditBox *aEditbox)
+ : Menubar()
+{
+ editor = aEditor;
+ editbox = aEditbox;
+ populate_color_scheme_menu();
+ init(mainMenu);
+}
+
+void MMMenubar::refresh_screen()
+{
+ editor->refresh(true);
+}
+
+PopupMenu *MMMenubar::create_popupmenu(PulldownMenu mnu)
+{
+ return new MMPopupMenu(editor, editbox, NULL, mnu);
+}
+
+//////////////////// Populate the ColorScheme Menu /////////////////////////
+
+bool operator<(const MenuItem &a, const MenuItem &b)
+{
+ u8string as = a.label, bs = b.label;
+ return strcmp(as.erase_char('~').toupper_ascii().c_str(),
+ bs.erase_char('~').toupper_ascii().c_str()) < 0;
+}
+
+static u8string get_color_scheme_title(const char *filename)
+{
+#define MAX_LINE_LEN 1024
+ FILE *fp;
+ if ((fp = fopen(filename, "r")) != NULL) {
+ char line[MAX_LINE_LEN];
+ int line_no = 0;
+ while (fgets(line, MAX_LINE_LEN, fp) && line_no++ < 5) {
+ const char *pos;
+ if ((pos = strstr(line, "Title:")) != NULL) {
+ fclose(fp);
+ return u8string(pos + strlen("Title:")).trim();
+ }
+ }
+ fclose(fp);
+ }
+ return "";
+#undef MAX_LINE_LEN
+}
+
+static bool theme_already_listed(const char *theme_name)
+{
+ int i = 0;
+ while (ColorScheme[i].action) {
+ if (ColorScheme[i].command_parameter3 &&
+ STREQ(ColorScheme[i].command_parameter3, theme_name))
+ return true;
+ i++;
+ }
+ return false;
+}
+
+void MMMenubar::populate_color_scheme_menu()
+{
+ int menu_pos = 0;
+ while (ColorScheme[menu_pos].action) menu_pos++;
+ int menu_start = menu_pos;
+
+ for (int i = 0; i < 2; i++) {
+ DIR *dir;
+ dirent *ent;
+ const char *dirstr = get_cfg_filename((i == 0) ? USER_THEMES_DIR : SYSTEM_THEMES_DIR);
+ if ((dir = opendir(dirstr)) == NULL)
+ continue;
+ while ((ent = readdir(dir)) != NULL) {
+ const char *d_name = ent->d_name;
+ u8string desc;
+ desc.cformat("File: %s%s", dirstr, d_name);
+
+ if (STREQ(d_name, "default.thm") && !ColorScheme[0].desc)
+ ColorScheme[0].desc = strdup(desc.c_str());
+
+ if (strstr(d_name, ".thm") && !strchr(d_name, '~')
+ && !theme_already_listed(d_name)) {
+ ColorScheme[menu_pos].action = "xxx";
+ ColorScheme[menu_pos].command_parameter1 = CMD_SETTHEME;
+ ColorScheme[menu_pos].command_parameter3 = strdup(d_name);
+ ColorScheme[menu_pos].state_id = STT_THEME_START + menu_pos;
+ ColorScheme[menu_pos].desc = strdup(desc.c_str());
+
+ u8string filepath = dirstr; filepath += d_name;
+ u8string title = get_color_scheme_title(filepath.c_str());
+ if (title.empty()) {
+ char *label = strdup(d_name);
+ // cut the file extension
+ if (strchr(label, '.'))
+ *strchr(label, '.') = '\0';
+ ColorScheme[menu_pos].label = label;
+ } else {
+ ColorScheme[menu_pos].label = strdup(title.c_str());
+ }
+
+ menu_pos++;
+ }
+ }
+ closedir(dir);
+ }
+ std::sort(&ColorScheme[menu_start], &ColorScheme[menu_pos]);
+
+ ColorScheme[menu_pos++].action = "---";
+ ColorScheme[menu_pos].action = "xxx";
+ ColorScheme[menu_pos].label = HELP_ITEM;
+ ColorScheme[menu_pos].command_parameter1 = CMD_HELP;
+ ColorScheme[menu_pos].command_parameter3 = HELP_TOPIC_COLORS_STR;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+Menubar *create_main_menubar(Editor *aEdtr, EditBox *aEdtbx)
+{
+ return new MMMenubar(aEdtr, aEdtbx);
+}
+
diff --git a/menus.h b/menus.h
new file mode 100644
index 0000000..89681a1
--- /dev/null
+++ b/menus.h
@@ -0,0 +1,12 @@
+#ifndef BDE_MENUS_H
+#define BDE_MENUS_H
+
+#include "basemenu.h"
+
+#include "editbox.h"
+#include "editor.h"
+
+Menubar *create_main_menubar(Editor *, EditBox *);
+
+#endif
+
diff --git a/missing b/missing
new file mode 100755
index 0000000..d46f79f
--- /dev/null
+++ b/missing
@@ -0,0 +1,198 @@
+#! /bin/sh
+# Common stub for a few missing GNU programs while installing.
+# Copyright (C) 1996, 1997, 2001 Free Software Foundation, Inc.
+# Franc,ois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+if test $# -eq 0; then
+ echo 1>&2 "Try \`$0 --help' for more information"
+ exit 1
+fi
+
+# In the cases where this matters, `missing' is being run in the
+# srcdir already.
+if test -f configure.in; then
+ configure_ac=configure.ac
+else
+ configure_ac=configure.in
+fi
+
+case "$1" in
+
+ -h|--h|--he|--hel|--help)
+ echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
+error status if there is no known handling for PROGRAM.
+
+Options:
+ -h, --help display this help and exit
+ -v, --version output version information and exit
+
+Supported PROGRAM values:
+ aclocal touch file \`aclocal.m4'
+ autoconf touch file \`configure'
+ autoheader touch file \`config.h.in'
+ automake touch all \`Makefile.in' files
+ bison create \`y.tab.[ch]', if possible, from existing .[ch]
+ flex create \`lex.yy.c', if possible, from existing .c
+ lex create \`lex.yy.c', if possible, from existing .c
+ makeinfo touch the output file
+ yacc create \`y.tab.[ch]', if possible, from existing .[ch]"
+ ;;
+
+ -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+ echo "missing - GNU libit 0.0"
+ ;;
+
+ -*)
+ echo 1>&2 "$0: Unknown \`$1' option"
+ echo 1>&2 "Try \`$0 --help' for more information"
+ exit 1
+ ;;
+
+ aclocal)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified \`acinclude.m4' or \`$configure_ac'. You might want
+ to install the \`Automake' and \`Perl' packages. Grab them from
+ any GNU archive site."
+ touch aclocal.m4
+ ;;
+
+ autoconf)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified \`$configure_ac'. You might want to install the
+ \`Autoconf' and \`GNU m4' packages. Grab them from any GNU
+ archive site."
+ touch configure
+ ;;
+
+ autoheader)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified \`acconfig.h' or \`$configure_ac'. You might want
+ to install the \`Autoconf' and \`GNU m4' packages. Grab them
+ from any GNU archive site."
+ files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' $configure_ac`
+ test -z "$files" && files="config.h"
+ touch_files=
+ for f in $files; do
+ case "$f" in
+ *:*) touch_files="$touch_files "`echo "$f" |
+ sed -e 's/^[^:]*://' -e 's/:.*//'`;;
+ *) touch_files="$touch_files $f.in";;
+ esac
+ done
+ touch $touch_files
+ ;;
+
+ automake)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified \`Makefile.am', \`acinclude.m4' or \`$configure_ac'.
+ You might want to install the \`Automake' and \`Perl' packages.
+ Grab them from any GNU archive site."
+ find . -type f -name Makefile.am -print |
+ sed 's/\.am$/.in/' |
+ while read f; do touch "$f"; done
+ ;;
+
+ bison|yacc)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified a \`.y' file. You may need the \`Bison' package
+ in order for those modifications to take effect. You can get
+ \`Bison' from any GNU archive site."
+ rm -f y.tab.c y.tab.h
+ if [ $# -ne 1 ]; then
+ eval LASTARG="\${$#}"
+ case "$LASTARG" in
+ *.y)
+ SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
+ if [ -f "$SRCFILE" ]; then
+ cp "$SRCFILE" y.tab.c
+ fi
+ SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
+ if [ -f "$SRCFILE" ]; then
+ cp "$SRCFILE" y.tab.h
+ fi
+ ;;
+ esac
+ fi
+ if [ ! -f y.tab.h ]; then
+ echo >y.tab.h
+ fi
+ if [ ! -f y.tab.c ]; then
+ echo 'main() { return 0; }' >y.tab.c
+ fi
+ ;;
+
+ lex|flex)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified a \`.l' file. You may need the \`Flex' package
+ in order for those modifications to take effect. You can get
+ \`Flex' from any GNU archive site."
+ rm -f lex.yy.c
+ if [ $# -ne 1 ]; then
+ eval LASTARG="\${$#}"
+ case "$LASTARG" in
+ *.l)
+ SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
+ if [ -f "$SRCFILE" ]; then
+ cp "$SRCFILE" lex.yy.c
+ fi
+ ;;
+ esac
+ fi
+ if [ ! -f lex.yy.c ]; then
+ echo 'main() { return 0; }' >lex.yy.c
+ fi
+ ;;
+
+ makeinfo)
+ echo 1>&2 "\
+WARNING: \`$1' is missing on your system. You should only need it if
+ you modified a \`.texi' or \`.texinfo' file, or any other file
+ indirectly affecting the aspect of the manual. The spurious
+ call might also be the consequence of using a buggy \`make' (AIX,
+ DU, IRIX). You might want to install the \`Texinfo' package or
+ the \`GNU make' package. Grab either from any GNU archive site."
+ file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
+ if test -z "$file"; then
+ file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
+ file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
+ fi
+ touch $file
+ ;;
+
+ *)
+ echo 1>&2 "\
+WARNING: \`$1' is needed, and you do not seem to have it handy on your
+ system. You might have modified some files without having the
+ proper tools for further handling them. Check the \`README' file,
+ it often tells you about the needed prerequirements for installing
+ this package. You may also peek at any GNU archive site, in case
+ some other package would contain this missing \`$1' program."
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/mk_wcwidth.cc b/mk_wcwidth.cc
new file mode 100644
index 0000000..c211976
--- /dev/null
+++ b/mk_wcwidth.cc
@@ -0,0 +1,296 @@
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2002-05-08 (Unicode 3.2)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+//#include <wchar.h>
+#include "mk_wcwidth.h"
+#define wchar_t unichar
+
+struct interval {
+ unsigned int first;
+ unsigned int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_wcwidth(wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated with "uniset +cat=Me +cat=Mn +cat=Cf +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ { 0x0300, 0x034F }, { 0x0360, 0x036F }, { 0x0483, 0x0486 },
+ { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
+ { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x0901, 0x0902 }, { 0x093C, 0x093C },
+ { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0954 },
+ { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC },
+ { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 },
+ { 0x0A02, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 },
+ { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 },
+ { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 },
+ { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 },
+ { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
+ { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
+ { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
+ { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA },
+ { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 },
+ { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 },
+ { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD },
+ { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 },
+ { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 },
+ { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC },
+ { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 },
+ { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 },
+ { 0x1160, 0x11FF }, { 0x1712, 0x1714 }, { 0x1732, 0x1734 },
+ { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x180B, 0x180E },
+ { 0x18A9, 0x18A9 }, { 0x200B, 0x200F }, { 0x202A, 0x202E },
+ { 0x2060, 0x2063 }, { 0x206A, 0x206F }, { 0x20D0, 0x20EA },
+ { 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xFB1E, 0xFB1E },
+ { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF },
+ { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 },
+ { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
+ { 0xE0020, 0xE007F }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2ffff)));
+}
+
+
+int mk_wcswidth(const wchar_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcwidth_cjk(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+static int mk_wcwidth_cjk(wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated with "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
+ static const struct interval ambiguous[] = {
+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
+ { 0x00AA, 0x00AA }, { 0x00AD, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x24FE },
+ { 0x2500, 0x254B }, { 0x2550, 0x2573 }, { 0x2580, 0x258F },
+ { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 },
+ { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD },
+ { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB },
+ { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF },
+ { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, { 0x260E, 0x260F },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xFFFD, 0xFFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+
+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
diff --git a/mk_wcwidth.h b/mk_wcwidth.h
new file mode 100644
index 0000000..9c38e5d
--- /dev/null
+++ b/mk_wcwidth.h
@@ -0,0 +1,9 @@
+#ifndef MK_WCWIDTH_H
+#define MK_WCWISTH_H
+
+#include "types.h"
+
+int mk_wcwidth(unichar ch);
+
+#endif
+
diff --git a/mkinstalldirs b/mkinstalldirs
new file mode 100755
index 0000000..4f58503
--- /dev/null
+++ b/mkinstalldirs
@@ -0,0 +1,40 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# $Id: mkinstalldirs,v 1.13 1999/01/05 03:18:55 bje Exp $
+
+errstatus=0
+
+for file
+do
+ set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+ shift
+
+ pathcomp=
+ for d
+ do
+ pathcomp="$pathcomp$d"
+ case "$pathcomp" in
+ -* ) pathcomp=./$pathcomp ;;
+ esac
+
+ if test ! -d "$pathcomp"; then
+ echo "mkdir $pathcomp"
+
+ mkdir "$pathcomp" || lasterr=$?
+
+ if test ! -d "$pathcomp"; then
+ errstatus=$lasterr
+ fi
+ fi
+
+ pathcomp="$pathcomp/"
+ done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/my_wctob.h b/my_wctob.h
new file mode 100644
index 0000000..404d9ed
--- /dev/null
+++ b/my_wctob.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef MY_WCTOB
+#define MY_WCTOB
+
+#include <config.h>
+
+// BTOWC and WCTOB are used for terminal encoding and we assume an iso-8859-8
+// encoding if the system lacks them.
+
+#if defined(HAVE_WCTOB) || defined(HAVE_BTOWC)
+# include <wchar.h>
+#endif
+
+#include "iso88598.h"
+
+#if defined(HAVE_BTOWC)
+# define BTOWC(b) btowc(b)
+#else
+# define BTOWC(b) iso88598_to_unicode(b)
+#endif
+
+#if defined(HAVE_WCTOB)
+# define WCTOB(w) wctob(w)
+#else
+# define WCTOB(w) unicode_to_iso88598(w)
+#endif
+
+#endif
+
diff --git a/pathnames.h b/pathnames.h
new file mode 100644
index 0000000..4b60ffe
--- /dev/null
+++ b/pathnames.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_PATHNAMES_H
+#define BDE_PATHNAMES_H
+
+#define USER_RC_FILE "~/.%P/%Prc"
+#define USER_TRANSTBL_FILE "~/.%P/transtab"
+#define USER_REPRTBL_FILE "~/.%P/reprtab"
+#define USER_ALTKBDTBL_FILE "~/.%P/kbdtab"
+#define USER_MANUAL_FILE "~/.%P/MANUAL.he"
+#define USER_UNICODE_DATA_FILE "~/.%P/UnicodeData.txt"
+#define USER_THEMES_DIR "~/.%P/themes/"
+
+#define SYSTEM_RC_FILE PKGDATADIR "/%Prc"
+#define SYSTEM_TRANSTBL_FILE PKGDATADIR "/transtab"
+#define SYSTEM_REPRTBL_FILE PKGDATADIR "/reprtab"
+#define SYSTEM_ALTKBDTBL_FILE PKGDATADIR "/kbdtab"
+#define SYSTEM_MANUAL_FILE PKGDATADIR "/MANUAL.he"
+#define SYSTEM_UNICODE_DATA_FILE PKGDATADIR "/UnicodeData.txt"
+#define SYSTEM_THEMES_DIR PKGDATADIR "/themes/"
+
+#endif
+
diff --git a/pgeresh.in b/pgeresh.in
new file mode 100644
index 0000000..6d6d94a
--- /dev/null
+++ b/pgeresh.in
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+TMPFL=
+
+if ! tty -s; then
+ TMPFL=`mktemp /tmp/geresh.XXXXXX` || TMPFL=/tmp/geresh.$$
+ cat > $TMPFL
+fi
+
+geresh $TMPFL "$@" </dev/tty >/dev/tty
+
+if ! tty -s <&1; then
+ cat $TMPFL
+fi
+
+[ -n "$TMPFL" ] && rm -f $TMPFL
diff --git a/point.h b/point.h
new file mode 100644
index 0000000..4b10146
--- /dev/null
+++ b/point.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_POINT_H
+#define BDE_POINT_H
+
+// struct Point represents a point in the EditBox buffer. It consists of a
+// paragraph number and a character index within this paragraphs. The cursor
+// itself is of type Point.
+
+struct Point {
+
+ int para;
+ idx_t pos;
+
+ Point(int para, idx_t pos) {
+ this->para = para;
+ this->pos = pos;
+ }
+
+ Point() {
+ zero();
+ }
+
+ void zero() {
+ para = 0;
+ pos = 0;
+ }
+
+ void swap(Point &p) {
+ Point tmp = p;
+ p = *this;
+ *this = tmp;
+ }
+
+ bool operator< (const Point &p) const {
+ return para < p.para || (para == p.para && pos < p.pos);
+ }
+ bool operator== (const Point &p) const {
+ return para == p.para && pos == p.pos;
+ }
+ bool operator> (const Point &p) const {
+ return !(*this == p || *this < p);
+ }
+};
+
+#endif
+
diff --git a/question.cc b/question.cc
new file mode 100644
index 0000000..b88ae47
--- /dev/null
+++ b/question.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "question.h"
+
+Question::Question(const char *aText)
+ : Label(aText)
+{
+ yes_chars.init_from_utf8(_("yY"));
+ no_chars.init_from_utf8(_("nN"));
+ set_modal();
+ positive_answer = false;
+}
+
+bool Question::handle_event(const Event &evt)
+{
+ if (evt.is_literal()) {
+ if (yes_chars.has_char(evt.ch)) {
+ positive_answer = true;
+ end_modal();
+ return true;
+ }
+ if (no_chars.has_char(evt.ch)) {
+ positive_answer = false;
+ end_modal();
+ return true;
+ }
+ }
+ return Label::handle_event(evt);
+}
+
diff --git a/question.h b/question.h
new file mode 100644
index 0000000..be1dc94
--- /dev/null
+++ b/question.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_QUESTION_H
+#define BDE_QUESTION_H
+
+#include "label.h"
+
+// Question is a simple modal widget that displays a y/n question and waits
+// for an answer. We use gettext() to retrieve the possible "y"es and "n"o
+// characters.
+
+class Question : public Label {
+
+ bool positive_answer;
+ unistring yes_chars; // string of localized "y"es characters
+ unistring no_chars; // string of localized "n"o characters
+
+public:
+
+ Question(const char *aText);
+ virtual bool handle_event(const Event &evt);
+ bool get_answer() const { return positive_answer; }
+};
+
+#endif
+
diff --git a/reprtab b/reprtab
new file mode 100644
index 0000000..88f9db3
--- /dev/null
+++ b/reprtab
@@ -0,0 +1,121 @@
+# This file contains representations for some characters and concepts.
+
+0009 '·' # TAB
+05BE '-' # MAQAF
+2028 '\' # UNICODE LINE SEPARATOR
+
+200E '>' # LRM
+200F '<' # RLM
+202A '[' # LRE
+202B ']' # RLE
+202C '\' # PDF
+202D '{' # LRO
+202E '}' # RLO
+
+# Hebrew points
+
+# תנועות קטנות (קצרות) או חטופות קיבלו אות אנגלית קטנה; תנועות גדולות
+# (ארוכות) קיבלו אות אנגלית גדולה. למשל, חולם היא תנועה גדולה, לכן O,
+# ואילו חטף קמץ היא תנועה חטופה ולכן o.
+#
+# לרוע המזל, בשיטה הזו אי אפשר להבדיל בין חטף סגול לבין סגול ובין חטף
+# פתח לבין פתח, לכן חטף סגול מיוצג ^ (=שלוש נקודות מחוברות) וחטף פתח _.
+#
+# זכור שתמיד תוכל ללחוץ C-M-i כדי לקרוא את תיאור התו.
+
+05B0 ':' # HEBREW POINT SHEVA
+05B1 '^' # HEBREW POINT HATAF SEGOL
+05B2 '_' # HEBREW POINT HATAF PATAH
+05B3 'o' # HEBREW POINT HATAF QAMATS
+05B4 'i' # HEBREW POINT HIRIQ
+05B5 'E' # HEBREW POINT TSERE
+05B6 'e' # HEBREW POINT SEGOL
+05B7 'a' # HEBREW POINT PATAH
+05B8 'A' # HEBREW POINT QAMATS
+05B9 'O' # HEBREW POINT HOLAM
+05BB 'u' # HEBREW POINT QUBUTS
+05BC '*' # HEBREW POINT DAGESH OR MAPIQ (or shuruq)
+05BD '|' # HEBREW POINT METEG
+05BF '~' # HEBREW POINT RAFE
+05C1 's' # HEBREW POINT SHIN_DOT
+05C2 'z' # HEBREW POINT SIN_DOT
+05C4 '.' # HEBREW POINT UPPER_DOT
+
+0332 '_' # NON-SPACING UNDERSCORE
+
+# Cantillation marks
+
+# אם אתה משתמש בטעמי המקרא, ייצג אותם כרצונך. העורך מציג אותם בצבע שונה
+# מסימני הניקוד, ולכן תוכל לנצל שוב תווים שכבר השתמשת בהם.
+
+0591 'c' # HEBREW ACCENT ETNAHTA
+0592 'c' # HEBREW ACCENT SEGOL
+0593 'c' # HEBREW ACCENT SHALSHELET
+0594 'c' # HEBREW ACCENT ZAQEF QATAN
+0595 'c' # HEBREW ACCENT ZAQEF GADOL
+0596 'c' # HEBREW ACCENT TIPEHA
+0597 'c' # HEBREW ACCENT REVIA
+0598 'c' # HEBREW ACCENT ZARQA
+0599 'c' # HEBREW ACCENT PASHTA
+059A 'c' # HEBREW ACCENT YETIV
+059B 'c' # HEBREW ACCENT TEVIR
+059C 'c' # HEBREW ACCENT GERESH
+059D 'c' # HEBREW ACCENT GERESH MUQDAM
+059E 'c' # HEBREW ACCENT GERSHAYIM
+059F 'c' # HEBREW ACCENT QARNEY PARA
+05A0 'c' # HEBREW ACCENT TELISHA GEDOLA
+05A1 'c' # HEBREW ACCENT PAZER
+05A3 'c' # HEBREW ACCENT MUNAH
+05A4 'c' # HEBREW ACCENT MAHAPAKH
+05A5 'c' # HEBREW ACCENT MERKHA
+05A6 'c' # HEBREW ACCENT MERKHA KEFULA
+05A7 'c' # HEBREW ACCENT DARGA
+05A8 'c' # HEBREW ACCENT QADMA
+05A9 'c' # HEBREW ACCENT TELISHA QETANA
+05AA 'c' # HEBREW ACCENT YERAH BEN YOMO
+05AB 'c' # HEBREW ACCENT OLE
+05AC 'c' # HEBREW ACCENT ILUY
+05AD 'c' # HEBREW ACCENT DEHI
+05AE 'c' # HEBREW ACCENT ZINOR
+05AF 'c' # HEBREW MARK MASORA CIRCLE
+
+# Arabic harakats
+
+0x064B 'A' # ARABIC FATHATAN
+0x064C 'U' # ARABIC DAMMATAN
+0x064D 'I' # ARABIC KASRATAN
+0x064E 'a' # ARABIC FATHA
+0x064F 'u' # ARABIC DAMMA
+0x0650 'i' # ARABIC KASRA
+0x0651 '*' # ARABIC SHADDA
+0x0652 ':' # ARABIC SUKUN
+0x0670 '|' # ARABIC LETTER SUPERSCRIPT ALEF
+
+#
+# ALL OF THE FOLLOWING ARE PSEUDO CHARACTERS
+#
+
+'?' '?' # CHARACTER NOT IN TERMINAL CHARSET
+'^' '^' # CONTROL CHARACTER (ASCII 0..31)
+''' ''' # NON SPACING MARK
+'A' 'A' # WIDE CHARACTERS (E.G. ASIAN IDEOGRAPHS)
+
+#
+# VISUAL INDICATORS
+#
+
+'$' '$' # LINE IS TRIMMED
+'/' '/' # LINE IS WRAPPED (RTL)
+'\' '\' # LINE IS WRAPPED (LTR)
+
+#
+# PARAGRAPH ENDINGS
+#
+
+'¶' '¶' # UNICODE PARAGRAPH SEPARATOR
+'µ' 'µ' # DOS LINE END (CR+LF)
+'«' '«' # UNIX LTR LINE END (LF)
+'»' '»' # UNIX RTL LINE END (LF)
+'@' '@' # MAC LINE END (CR)
+'¬' '¬' # NO LINE END
+
diff --git a/scrollbar.cc b/scrollbar.cc
new file mode 100644
index 0000000..9b42899
--- /dev/null
+++ b/scrollbar.cc
@@ -0,0 +1,96 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "scrollbar.h"
+#include "themes.h"
+
+Scrollbar::Scrollbar()
+{
+ create_window();
+ scrollok(wnd, 0);
+ total_size = 0;
+ page_size = 0;
+ page_pos = 0;
+ dirty = true;
+}
+
+void Scrollbar::set_page_size(int sz)
+{
+ if (sz != page_size) {
+ page_size = sz;
+ invalidate_view();
+ }
+}
+
+void Scrollbar::set_total_size(int sz)
+{
+ if (sz != total_size) {
+ total_size = sz;
+ invalidate_view();
+ }
+}
+
+void Scrollbar::set_page_pos(int pos)
+{
+ if (pos != page_pos) {
+ page_pos = pos;
+ invalidate_view();
+ }
+}
+
+void Scrollbar::update()
+{
+ if (!dirty)
+ return;
+
+ int thumb_pos = 0;
+ if (page_pos > 0)
+ thumb_pos = (int)((double(page_pos)/double(total_size))*window_height());
+ int thumb_size = (int)((double)page_size/double(total_size)*window_height()+1);
+ if (thumb_size > window_height())
+ thumb_size = window_height();
+ if (page_size >= total_size) // don't show if we don't have a reason
+ thumb_size = 0;
+ if (thumb_size == window_height())
+ thumb_size--;
+ if (page_pos + page_size >= total_size)
+ thumb_pos = window_height() - thumb_size;
+
+ werase(wnd);
+ attribute_t base_attr = get_attr(SCROLLBAR_ATTR);
+ attribute_t thumb_attr = get_attr(SCROLLBAR_THUMB_ATTR);
+ for (int i = 0; i < window_height(); i++) {
+ if (i >= thumb_pos && i < thumb_pos + thumb_size) {
+ wattrset(wnd, thumb_attr);
+ mvwhline(wnd, i, 0, ' ', 1);
+ } else {
+ wattrset(wnd, base_attr);
+ mvwhline(wnd, i, 0, terminal::graphical_boxes ? ACS_VLINE : '|', 1);
+ }
+ }
+
+ wnoutrefresh(wnd);
+ dirty = false;
+}
+
+void Scrollbar::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ invalidate_view();
+}
+
diff --git a/scrollbar.h b/scrollbar.h
new file mode 100644
index 0000000..99ab6c1
--- /dev/null
+++ b/scrollbar.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_SCROLLBAR_H
+#define BDE_SCROLLBAR_H
+
+#include "types.h"
+#include "widget.h"
+
+class Scrollbar : public Widget {
+
+protected:
+
+ int total_size;
+ int page_size;
+ int page_pos;
+ bool dirty;
+
+public:
+
+ Scrollbar();
+ void set_total_size(int sz);
+ void set_page_size(int sz);
+ void set_page_pos(int pos);
+
+ virtual void update();
+ virtual void invalidate_view() { dirty = true; }
+ virtual bool is_dirty() const { return dirty; }
+ virtual void resize(int lines, int columns, int y, int x);
+};
+
+#endif
+
diff --git a/shaping.cc b/shaping.cc
new file mode 100644
index 0000000..c10f266
--- /dev/null
+++ b/shaping.cc
@@ -0,0 +1,368 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "shaping.h"
+#include "bidi.h"
+#include "univalues.h"
+
+struct charinfo {
+ unichar ccode;
+ char cclass;
+ unichar isolated;
+ unichar final;
+ unichar initial;
+ unichar medial;
+};
+
+static charinfo infos[] = {
+{ 0x0621, 'U', 0xFE80, 0x0000, 0x0000, 0x0000 },
+{ 0x0622, 'R', 0xFE81, 0xFE82, 0x0000, 0x0000 },
+{ 0x0623, 'R', 0xFE83, 0xFE84, 0x0000, 0x0000 },
+{ 0x0624, 'R', 0xFE85, 0xFE86, 0x0000, 0x0000 },
+{ 0x0625, 'R', 0xFE87, 0xFE88, 0x0000, 0x0000 },
+{ 0x0626, 'D', 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C },
+{ 0x0627, 'R', 0xFE8D, 0xFE8E, 0x0000, 0x0000 },
+{ 0x0628, 'D', 0xFE8F, 0xFE90, 0xFE91, 0xFE92 },
+{ 0x0629, 'R', 0xFE93, 0xFE94, 0x0000, 0x0000 },
+{ 0x062A, 'D', 0xFE95, 0xFE96, 0xFE97, 0xFE98 },
+{ 0x062B, 'D', 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C },
+{ 0x062C, 'D', 0xFE9D, 0xFE9E, 0xFE9F, 0xFEA0 },
+{ 0x062D, 'D', 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4 },
+{ 0x062E, 'D', 0xFEA5, 0xFEA6, 0xFEA7, 0xFEA8 },
+{ 0x062F, 'R', 0xFEA9, 0xFEAA, 0x0000, 0x0000 },
+{ 0x0630, 'R', 0xFEAB, 0xFEAC, 0x0000, 0x0000 },
+{ 0x0631, 'R', 0xFEAD, 0xFEAE, 0x0000, 0x0000 },
+{ 0x0632, 'R', 0xFEAF, 0xFEB0, 0x0000, 0x0000 },
+{ 0x0633, 'D', 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4 },
+{ 0x0634, 'D', 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8 },
+{ 0x0635, 'D', 0xFEB9, 0xFEBA, 0xFEBB, 0xFEBC },
+{ 0x0636, 'D', 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0 },
+{ 0x0637, 'D', 0xFEC1, 0xFEC2, 0xFEC3, 0xFEC4 },
+{ 0x0638, 'D', 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8 },
+{ 0x0639, 'D', 0xFEC9, 0xFECA, 0xFECB, 0xFECC },
+{ 0x063A, 'D', 0xFECD, 0xFECE, 0xFECF, 0xFED0 },
+{ 0x063B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x063C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x063D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x063E, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x063F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0640, 'D', 0x0640, 0x0640, 0x0640, 0x0640 },
+{ 0x0641, 'D', 0xFED1, 0xFED2, 0xFED3, 0xFED4 },
+{ 0x0642, 'D', 0xFED5, 0xFED6, 0xFED7, 0xFED8 },
+{ 0x0643, 'D', 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC },
+{ 0x0644, 'D', 0xFEDD, 0xFEDE, 0xFEDF, 0xFEE0 },
+{ 0x0645, 'D', 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4 },
+{ 0x0646, 'D', 0xFEE5, 0xFEE6, 0xFEE7, 0xFEE8 },
+{ 0x0647, 'D', 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC },
+{ 0x0648, 'R', 0xFEED, 0xFEEE, 0x0000, 0x0000 },
+{ 0x0649, 'D', 0xFEEF, 0xFEF0, 0xFBE8, 0xFBE9 },
+{ 0x064A, 'D', 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4 },
+{ 0x064B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x064C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x064D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x064E, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x064F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0650, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0651, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0652, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0653, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0654, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0655, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0656, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0657, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0658, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0659, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065A, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065E, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x065F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0660, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0661, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0662, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0663, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0664, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0665, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0666, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0667, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0668, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0669, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066A, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066E, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x066F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0670, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0671, 'R', 0xFB50, 0xFB51, 0x0000, 0x0000 },
+{ 0x0672, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0673, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0674, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0675, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0676, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0677, 'R', 0xFBDD, 0xFFFD, 0x0000, 0x0000 },
+{ 0x0678, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0679, 'D', 0xFB66, 0xFB67, 0xFB68, 0xFB69 },
+{ 0x067A, 'D', 0xFB5E, 0xFB5F, 0xFB60, 0xFB61 },
+{ 0x067B, 'D', 0xFB52, 0xFB53, 0xFB54, 0xFB55 },
+{ 0x067C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x067D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x067E, 'D', 0xFB56, 0xFB57, 0xFB58, 0xFB59 },
+{ 0x067F, 'D', 0xFB62, 0xFB63, 0xFB64, 0xFB65 },
+{ 0x0680, 'D', 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D },
+{ 0x0681, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0682, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0683, 'D', 0xFB76, 0xFB77, 0xFB78, 0xFB79 },
+{ 0x0684, 'D', 0xFB72, 0xFB73, 0xFB74, 0xFB75 },
+{ 0x0685, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0686, 'D', 0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D },
+{ 0x0687, 'D', 0xFB7E, 0xFB7F, 0xFB80, 0xFB81 },
+{ 0x0688, 'R', 0xFB88, 0xFB89, 0x0000, 0x0000 },
+{ 0x0689, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x068A, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x068B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x068C, 'R', 0xFB84, 0xFB85, 0x0000, 0x0000 },
+{ 0x068D, 'R', 0xFB82, 0xFB83, 0x0000, 0x0000 },
+{ 0x068E, 'R', 0xFB86, 0xFB87, 0x0000, 0x0000 },
+{ 0x068F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0690, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0691, 'R', 0xFB8C, 0xFB8D, 0x0000, 0x0000 },
+{ 0x0692, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0693, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0694, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0695, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0696, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0697, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x0698, 'R', 0xFB8A, 0xFB8B, 0x0000, 0x0000 },
+{ 0x0699, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069A, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069B, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069C, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069D, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069E, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x069F, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A0, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A1, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A2, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A3, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A4, 'D', 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D },
+{ 0x06A5, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A6, 'D', 0xFB6E, 0xFB6F, 0xFB70, 0xFB71 },
+{ 0x06A7, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A8, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06A9, 'D', 0xFB8E, 0xFB8F, 0xFB90, 0xFB91 },
+{ 0x06AA, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06AB, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06AC, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06AD, 'D', 0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6 },
+{ 0x06AE, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06AF, 'D', 0xFB92, 0xFB93, 0xFB94, 0xFB95 },
+{ 0x06B0, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B1, 'D', 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D },
+{ 0x06B2, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B3, 'D', 0xFB96, 0xFB97, 0xFB98, 0xFB99 },
+{ 0x06B4, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B5, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B6, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B7, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B8, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06B9, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06BA, 'D', 0xFB9E, 0xFB9F, 0xFFFD, 0xFFFD },
+{ 0x06BB, 'D', 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3 },
+{ 0x06BC, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06BD, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06BE, 'D', 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD },
+{ 0x06BF, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06C0, 'R', 0xFBA4, 0xFBA5, 0x0000, 0x0000 },
+{ 0x06C1, 'D', 0xFBA6, 0xFBA7, 0xFBA8, 0xFBA9 },
+{ 0x06C2, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06C3, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06C4, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06C5, 'R', 0xFBE0, 0xFBE1, 0x0000, 0x0000 },
+{ 0x06C6, 'R', 0xFBD9, 0xFBDA, 0x0000, 0x0000 },
+{ 0x06C7, 'R', 0xFBD7, 0xFBD8, 0x0000, 0x0000 },
+{ 0x06C8, 'R', 0xFBDB, 0xFBDC, 0x0000, 0x0000 },
+{ 0x06C9, 'R', 0xFBE2, 0xFBE3, 0x0000, 0x0000 },
+{ 0x06CA, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06CB, 'R', 0xFBDE, 0xFBDF, 0x0000, 0x0000 },
+{ 0x06CC, 'D', 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF },
+{ 0x06CD, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06CE, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06CF, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06D0, 'D', 0xFBE4, 0xFBE5, 0xFBE6, 0xFBE7 },
+{ 0x06D1, 'X', 0x0000, 0x0000, 0x0000, 0x0000 },
+{ 0x06D2, 'R', 0xFBAE, 0xFBAF, 0x0000, 0x0000 },
+{ 0x06D3, 'R', 0xFBB0, 0xFBB1, 0x0000, 0x0000 }
+};
+
+#define TBLMIN 0x0621
+#define TBLMAX 0x06D3
+
+static inline bool is_d(unichar ch) {
+ if (ch >= TBLMIN && ch <= TBLMAX) {
+ return (infos[ch - TBLMIN].cclass == 'D');
+ }
+ return false;
+}
+
+static inline bool is_r(unichar ch) {
+ if (ch >= TBLMIN && ch <= TBLMAX) {
+ return (infos[ch - TBLMIN].cclass == 'R');
+ }
+ return false;
+}
+
+static inline bool is_rjc(unichar ch) {
+ if (ch == UNI_ZWJ)
+ return true;
+ if (ch >= TBLMIN && ch <= TBLMAX) {
+ return (infos[ch - TBLMIN].cclass == 'D');
+ }
+ return false;
+}
+
+static inline bool is_ljc(unichar ch) {
+ if (ch == UNI_ZWJ)
+ return true;
+ if (ch >= TBLMIN && ch <= TBLMAX) {
+ return (infos[ch - TBLMIN].cclass == 'D'
+ || infos[ch - TBLMIN].cclass == 'R');
+ }
+ return false;
+}
+
+static inline charinfo *get_info(unichar ch) {
+ if (ch >= TBLMIN && ch <= TBLMAX) {
+ return &infos[ch - TBLMIN];
+ }
+ return NULL;
+}
+
+bool is_shaping_transparent(unichar ch) {
+ return BiDi::is_nsm(ch);
+}
+
+// shape() - this is a temporary and a very inefficient implementation
+// of Arabic joining described in section 8.2 of the Unicode standard.
+//
+// :TODO: optimize.
+
+int shape(unichar *s, int len, attribute_t *attributes)
+{
+ if (!len)
+ return len;
+ unichar a = 0, b = 0, c = 0;
+ for (int i = len - 1; i >= 0; i--) {
+ b = s[i];
+ unichar &chref = s[i];
+
+ c = 0;
+ while (i > 0 && is_shaping_transparent(s[i-1]))
+ i--;
+ if (i > 0)
+ c = s[i-1];
+
+ if (is_r(b)) {
+ if (is_rjc(a)) {
+ chref = get_info(b)->final;
+ }
+ } else if (is_d(b)) {
+ if (is_rjc(a) && is_ljc(c)) {
+ chref = get_info(b)->medial;
+ } else if (is_rjc(a) && !is_ljc(c)) {
+ chref = get_info(b)->final;
+ } else if (!is_rjc(a) && is_ljc(c)) {
+ chref = get_info(b)->initial;
+ }
+ }
+
+ a = b;
+ }
+
+ return ligate(s, len, attributes);
+}
+
+// ligate() - do LAM-ALEF ligatures. returns the new length of the string.
+
+int ligate(unichar *s, int len, attribute_t *attributes)
+{
+#define LAM_L 0xFEDF
+#define LAM_M 0xFEE0
+
+#define ALEF_MADDA_R 0xFE82
+#define ALEF_HAMZA_ABOVE_R 0xFE84
+#define ALEF_HAMZA_BELOW_R 0xFE88
+#define ALEF_R 0xFE8E
+
+#define LAMALEF_MADDA_I 0xFEF5
+#define LAMALEF_MADDA_R 0xFEF6
+#define LAMALEF_HAMZA_ABOVE_I 0xFEF7
+#define LAMALEF_HAMZA_ABOVE_R 0xFEF8
+#define LAMALEF_HAMZA_BELOW_I 0xFEF9
+#define LAMALEF_HAMZA_BELOW_R 0xFEFA
+#define LAMALEF_I 0xFEFB
+#define LAMALEF_R 0xFEFC
+
+ int new_len = len;
+ bool may_start = false;
+ int lig_start = 0; // silence the compiler
+
+ for (int i = len - 1; i >= 0; i--) {
+
+ if (i > 0 && (s[i] == LAM_L || s[i] == LAM_M)) {
+ lig_start = i;
+ may_start = true;
+ }
+ else if (may_start) {
+ if (s[i] == ALEF_MADDA_R ||
+ s[i] == ALEF_HAMZA_ABOVE_R ||
+ s[i] == ALEF_HAMZA_BELOW_R ||
+ s[i] == ALEF_R)
+ {
+ int rlig = (s[lig_start] == LAM_M) ? 1 : 0;
+ switch (s[i]) {
+ case ALEF_MADDA_R:
+ s[i] = LAMALEF_MADDA_I + rlig; break;
+ case ALEF_HAMZA_ABOVE_R:
+ s[i] = LAMALEF_HAMZA_ABOVE_I + rlig; break;
+ case ALEF_HAMZA_BELOW_R:
+ s[i] = LAMALEF_HAMZA_BELOW_I + rlig; break;
+ case ALEF_R:
+ s[i] = LAMALEF_I + rlig; break;
+ }
+
+ for (int j = lig_start; j < new_len - 1; j++)
+ s[j] = s[j+1];
+ if (attributes) {
+ for (int j = lig_start; j < new_len - 1; j++)
+ attributes[j] = attributes[j+1];
+ }
+
+ new_len--; // we deleted a LAM
+
+ } else {
+ if (!is_shaping_transparent(s[i]))
+ may_start = false;
+ }
+ }
+ }
+
+ return new_len;
+}
+
diff --git a/shaping.h b/shaping.h
new file mode 100644
index 0000000..91feb4b
--- /dev/null
+++ b/shaping.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_SHAPING_H
+#define BDE_SHAPING_H
+
+#include "types.h"
+#include "widget.h" // typedef attribute_t
+
+int shape(unichar *s, int len, attribute_t *attributes = NULL);
+
+int ligate(unichar *s, int len, attribute_t *attributes = NULL);
+
+bool is_shaping_transparent(unichar ch);
+
+#endif
+
diff --git a/speller.cc b/speller.cc
new file mode 100644
index 0000000..f1b4c6b
--- /dev/null
+++ b/speller.cc
@@ -0,0 +1,862 @@
+#include <unistd.h> // exec, pipe, fork...
+#include <algorithm> // std::sort
+
+#include "speller.h"
+#include "mk_wcwidth.h"
+#include "converters.h"
+#include "editor.h"
+#include "dialogline.h"
+#include "dbg.h"
+
+// A Correction class encapsulates an incorrect word, its position
+// in the text, and a list of seggested corrections.
+
+class Correction {
+
+ bool valid;
+
+public:
+
+ Correction(const char *s, int aLine);
+ bool is_valid() { return valid; }
+
+ unistring incorrect;
+ // we also save a version of the word represented
+ // in the speller encoding, so we don't have to convert
+ // the word back later.
+ cstring incorrect_original;
+ std::vector<unistring> suggestions;
+
+ // The position of the incorrect word: line number and offset within.
+
+ int line;
+ int offset;
+
+ // hspell sometimes returns spelling-hints (short textual explanation
+ // of why the word is incorrect).
+
+ unistring hint;
+
+ void add_hint(const unistring &s) {
+ if (!hint.empty())
+ hint.push_back('\n');
+ hint.append(s);
+ }
+};
+
+// A Corrections class holds a list of Correction objects pertaining
+// to one paragraph of text.
+
+class Corrections {
+
+ std::vector<Correction *> array;
+
+ // A function object to sort the Correction objects by their offset
+ // within the paragraph.
+ struct cmp_corrections {
+ bool operator() (const Correction *a, const Correction *b) const {
+ return a->offset < b->offset;
+ }
+ };
+
+public:
+
+ Corrections() {}
+ ~Corrections();
+
+ void clear();
+ void add(Correction *crctn);
+ bool empty() const { return array.empty(); }
+ int size() const { return (int)array.size(); }
+ Correction *operator[] (int i)
+ { return array[i]; }
+
+ // The speller (e.g. hspell) may not report incorrect words in
+ // the order in which they appear in the paragraph. This is because
+ // hspell delegates the work to [ia]spell after it finishes reporting
+ // the incorrect Hebrew words. However, since we want to present
+ // the user the words in the right order, we have to sort them first.
+
+ void sort() {
+ std::sort(array.begin(), array.end(), cmp_corrections());
+ }
+};
+
+Corrections::~Corrections()
+{
+ clear();
+}
+
+void Corrections::clear()
+{
+ for (int i = 0; i < size(); i++)
+ delete array[i];
+ array.clear();
+}
+
+void Corrections::add(Correction *crctn)
+{
+ array.push_back(crctn);
+}
+
+// A Correction constructor parses an ispell-a line.
+//
+// The detailed description of the ispell-a protocol can be found
+// in the ispell man page. In short, when the speller finds an incorrect
+// word and has some spell suggestions, it returns:
+//
+// [&?] incorrect-word count offset: word, word, word, word
+//
+// When it has no suggestions, it returns:
+//
+// # <<incorrect>> <<offset>>
+//
+// If the protocol-line does not conform to the above syntaxes, we
+// ignore it and mark the object as invalid.
+
+Correction::Correction(const char *s, int aLine)
+{
+ if (*s != '&' && *s != '?' && *s != '#') {
+ valid = false;
+ return;
+ }
+ valid = true;
+ line = aLine;
+ offset = -1;
+
+ bool has_suggestions = (*s != '#');
+
+ const char *pos, *start;
+ start = pos = s + 2;
+ while (*pos != ' ')
+ pos++;
+ incorrect.init_from_utf8(start, pos);
+
+ offset = strtol(pos, (char **)&pos, 10);
+ if (has_suggestions)
+ offset = strtol(pos, (char **)&pos, 10);
+ // we sent the speller lines prefixed with "^", so we need
+ // to decrease by one.
+ offset--;
+
+ // the following post[1,2] tests are needed because
+ // hspell returns "?" instead of "#" when there are
+ // no suggestions.
+ if (has_suggestions && pos[1] && pos[2]) {
+ unistring word;
+ do {
+ start = pos += 2;
+ while (*pos && *pos != ',')
+ pos++;
+ word.init_from_utf8(start, pos);
+ suggestions.push_back(word);
+ } while (*pos);
+ }
+}
+
+//////////////////////////// SpellerWnd //////////////////////////////////
+
+SpellerWnd::SpellerWnd(Editor &aApp) :
+ app(aApp)
+{
+ create_window();
+ label.highlight();
+ label.set_text(_("Speller Results"));
+ // The following are the keys the user presses to select
+ // a spelling suggestion. These can be modified using gettext's
+ // message catalogs.
+ word_keys.init_from_utf8(
+ _("1234567890:;<=>@bcdefhijklmnopqstuvwxyz[\\]^_`"
+ "BCDEFHIJKLMNOPQSTUVWXYZ{|}~"));
+}
+
+void SpellerWnd::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ label.resize(1, columns, y, x);
+ editbox.resize(lines - 1, columns, y + 1, x);
+}
+
+void SpellerWnd::update()
+{
+ label.update();
+ editbox.update();
+}
+
+bool SpellerWnd::is_dirty() const
+{
+ return label.is_dirty() || editbox.is_dirty();
+}
+
+void SpellerWnd::invalidate_view()
+{
+ label.invalidate_view();
+ editbox.invalidate_view();
+}
+
+INTERACTIVE void SpellerWnd::layout_windows()
+{
+ app.layout_windows();
+}
+
+INTERACTIVE void SpellerWnd::refresh()
+{
+ app.refresh();
+}
+
+void SpellerWnd::clear()
+{
+ editbox.new_document();
+}
+
+void SpellerWnd::append(const unistring &us)
+{
+ editbox.insert_text(us);
+}
+
+void SpellerWnd::append(const char *s)
+{
+ unistring us;
+ us.init_from_utf8(s);
+ editbox.insert_text(us);
+}
+
+void SpellerWnd::end_menu(MenuResult result)
+{
+ menu_result = result;
+ finished = true;
+}
+
+INTERACTIVE void SpellerWnd::ignore_word()
+{
+ end_menu(splIgnore);
+}
+
+INTERACTIVE void SpellerWnd::add_to_dict()
+{
+ end_menu(splAdd);
+}
+
+INTERACTIVE void SpellerWnd::edit_replacement()
+{
+ end_menu(splEdit);
+}
+
+INTERACTIVE void SpellerWnd::abort_spelling()
+{
+ end_menu(splAbort);
+}
+
+INTERACTIVE void SpellerWnd::abort_spelling_restore_cursor()
+{
+ end_menu(splAbortRestoreCursor);
+}
+
+INTERACTIVE void SpellerWnd::set_global_decision()
+{
+ global_decision = true;
+ editbox.set_read_only(false);
+ editbox.move_beginning_of_buffer();
+ append(_("--GLOBAL DECISION--\n"));
+ editbox.set_read_only(true);
+}
+
+// handle_event() -
+//
+// A typical SpellerWnd window displays:
+//
+// (1) begging (2) begin (3) begun (4) bagging (5) beguine
+//
+// In brackets are the keys the user presses to choose a
+// spelling suggestion. We handle these keys here.
+
+bool SpellerWnd::handle_event(const Event &evt)
+{
+ if (Widget::handle_event(evt))
+ return true;
+ if (evt.is_literal()) {
+ int idx = word_keys.index(evt.ch);
+ if (idx != -1 && idx < (int)correction->suggestions.size()) {
+ suggestion_choice = idx;
+ end_menu(splChoice);
+ }
+ return true;
+ }
+ return editbox.handle_event(evt);
+}
+
+// exec_correction_menu() - Setup the SpellerWnd contents and then
+// execute a modal menu (using an event loop). It returns the user's
+// action.
+
+MenuResult SpellerWnd::exec_correction_menu(Correction &crctn)
+{
+ // we save the Correction object in a member variable because
+ // other methods (e.g. handle_event) use it.
+ correction = &crctn;
+
+ u8string title;
+ title.cformat(_("Suggestions for '%s'"),
+ u8string(correction->incorrect).c_str());
+ label.set_text(title.c_str());
+
+ editbox.set_read_only(false);
+ clear();
+ for (int i = 0; i < (int)correction->suggestions.size()
+ && i < word_keys.len(); i++)
+ {
+ u8string utf8_word(correction->suggestions[i]);
+ u8string utf8_key(word_keys.substr(i, 1));
+ u8string word_tmplt;
+ if (i != 0)
+ append("\xC2\xA0 "); // UNI_NO_BREAK_SPACE
+ word_tmplt.cformat(_("(%s)\xC2\xA0%s"),
+ utf8_key.c_str(), utf8_word.c_str());
+ append(word_tmplt.c_str());
+ }
+ if (correction->suggestions.empty())
+ append(_("No suggestions for this word."));
+ append("\n\n");
+ if (!correction->hint.empty()) {
+ append(correction->hint);
+ append("\n\n");
+ }
+ append(_("[SPC to leave unchanged, 'a' to add to private dictionary, "
+ "'r' to edit word, 'q' to exit and restore cursor, ^C to "
+ "exit and leave cursor, or one of the above characters "
+ "to replace. 'g' to make your decision global.]"));
+ editbox.set_read_only(true);
+ editbox.move_beginning_of_buffer();
+
+ global_decision = false;
+ finished = false;
+ while (!finished) {
+ Event evt;
+ app.update_terminal();
+ get_next_event(evt, editbox.wnd);
+ handle_event(evt);
+ }
+ return menu_result;
+}
+
+///////////////////////////// Speller ////////////////////////////////////
+
+#define SPELER_REPLACE_HISTORY 110
+
+// the following UNLOAD_SPELLER routine is a temporary hack to
+// a pipe problem (see TODO).
+static Speller *global_speller_instance = NULL;
+void UNLOAD_SPELLER()
+{
+ if (global_speller_instance)
+ global_speller_instance->unload();
+}
+
+// replace_table is a hash-table that matches any incorrect word
+// with its correct spelling. It is used to implement the "Replace
+// All" function. Also, when the value of the key is the empty
+// string, it means to ignore the word (that's how "Ignore All" is
+// implemented).
+
+std::map<unistring, unistring> replace_table;
+
+Speller::Speller(Editor &aApp, DialogLine &aDialog) :
+ app(aApp),
+ dialog(aDialog)
+{
+ loaded = false;
+ global_speller_instance = this;
+}
+
+// load() - loads the speller. it forks and execs the speller. it setups
+// pipes for communication.
+//
+// Warning: the code is not foolproof! it expects the child process to
+// print an identity string. if the child prints nothing, this function
+// hangs!
+
+bool Speller::load(const char *cmd, const char *encoding)
+{
+ if (is_loaded())
+ return true;
+
+ conv_to_speller =
+ ConverterFactory::get_converter_to(encoding);
+ conv_from_speller =
+ ConverterFactory::get_converter_from(encoding);
+ if (!conv_to_speller || !conv_from_speller) {
+ dialog.show_message_fmt(_("Can't find converter '%s'"), encoding);
+ return false;
+ }
+ conv_to_speller->enable_ilseq_repr();
+
+ dialog.show_message(_("Loading speller..."));
+ dialog.immediate_update();
+
+ if (pipe(fd_to_spl) < 0 || pipe(fd_from_spl) < 0) {
+ dialog.show_message(_("pipe() error"));
+ return false;
+ }
+ pid_t pid;
+ if ((pid = fork()) < 0) {
+ dialog.show_message(_("fork() error"));
+ return false;
+ }
+ if (pid == 0) {
+ DISABLE_SIGTSTP();
+ // we're in the child.
+ dup2(fd_to_spl[0], STDIN_FILENO);
+ dup2(fd_from_spl[1], STDOUT_FILENO);
+ dup2(fd_from_spl[1], STDERR_FILENO);
+
+ close(fd_from_spl[0]); close(fd_to_spl[0]);
+ close(fd_from_spl[1]); close(fd_to_spl[1]);
+
+ execlp("/bin/sh", "sh", "-c", cmd, NULL);
+
+ // write the error back to the parent
+ u8string err;
+ err.cformat(_("Error %d (%s)\n"), errno, strerror(errno));
+ write(STDOUT_FILENO, err.c_str(), err.size());
+ exit(1);
+ }
+
+ dialog.show_message(_("Waiting for the speller to finish loading..."));
+ dialog.immediate_update();
+
+ u8string identity = read_line();
+
+ if (identity.c_str()[0] != '@') {
+ dialog.show_message_fmt(_("Error: Not a speller: %s"),
+ identity.c_str());
+ unload();
+ return false;
+ } else {
+ // display the speller identity for a brief moment.
+ dialog.show_message(identity.c_str());
+ dialog.immediate_update();
+ sleep(1);
+ write_line("@ActivateExtendedProtocol\n"); // for future extensions :-)
+ dialog.show_message(_("Speller loaded OK."));
+ loaded = true;
+ return true;
+ }
+}
+
+void Speller::unload()
+{
+ if (loaded) {
+ close(fd_from_spl[0]); close(fd_to_spl[0]);
+ close(fd_from_spl[1]); close(fd_to_spl[1]);
+ delete conv_to_speller;
+ delete conv_from_speller;
+ loaded = false;
+ }
+}
+
+// convert_from_unistr() and convert_to_unistr() convert from unicode
+// to the speller encoding and vice versa.
+
+void convert_from_unistr(cstring &cstr, const unistring &str,
+ Converter *conv)
+{
+ char *buf = new char[str.len() * 6 + 1]; // Max UTF-8 seq is 6.
+ unichar *us_p = (unichar *)str.begin();
+ char *cs_p = buf;
+ conv->convert(&cs_p, &us_p, str.len());
+ cstr = cstring(buf, cs_p);
+}
+
+void convert_to_unistr(unistring &str, const cstring &cstr,
+ Converter *conv)
+{
+ str.resize(cstr.size());
+ unichar *us_p = (unichar *)str.begin();
+ char *cs_p = (char *)&*cstr.begin(); // convert iterator to pointer
+ conv->convert(&us_p, &cs_p, cstr.size());
+ str.resize(us_p - str.begin());
+}
+
+void Speller::add_to_dictionary(Correction &correction)
+{
+ replace_table[correction.incorrect] = unistring(); // "Ignore All"
+ cstring cstr;
+ cstr.cformat("*%s\n", correction.incorrect_original.c_str());
+ write_line(cstr.c_str());
+ write_line("#\n");
+}
+
+// interactive_correct() - let the user interactively correct the
+// spelling mistakes. For every incorrect word, it:
+//
+// 1. highlights the word
+// 2. calls exec_correction_menu() to display the menu
+// 3. acts based on the user action.
+//
+// returns 'false' if the user aborts.
+
+bool Speller::interactive_correct(Corrections &corrections,
+ EditBox &wedit,
+ SpellerWnd &splwnd,
+ bool &restore_cursor)
+{
+ for (int cur_crctn = 0; cur_crctn < corrections.size(); cur_crctn++)
+ {
+ Correction &correction = *corrections[cur_crctn];
+
+ MenuResult menu_result;
+ unistring replace_with;
+
+ if (replace_table.find(correction.incorrect) != replace_table.end()) {
+ replace_with = replace_table[correction.incorrect];
+ menu_result = splEdit;
+ } else {
+ // highlight the word
+ wedit.unset_primary_mark();
+ wedit.set_cursor_position(Point(correction.line,
+ correction.offset));
+ wedit.set_primary_mark();
+ for (int i = 0; i < correction.incorrect.len(); i++)
+ wedit.move_forward_char();
+
+ menu_result = splwnd.exec_correction_menu(correction);
+
+ if (menu_result == splChoice) {
+ replace_with = correction.suggestions[
+ splwnd.get_suggestion_choice()];
+ } else if (menu_result == splEdit) {
+ bool alt_kbd = wedit.get_alt_kbd();
+ replace_with = dialog.query(_("Replace with:"),
+ correction.incorrect, SPELER_REPLACE_HISTORY,
+ InputLine::cmpltOff, &alt_kbd);
+ wedit.set_alt_kbd(alt_kbd);
+ }
+ }
+
+ switch (menu_result) {
+ case splAbort:
+ restore_cursor = false;
+ return false;
+ break;
+ case splAbortRestoreCursor:
+ restore_cursor = true;
+ return false;
+ break;
+ case splIgnore:
+ if (splwnd.is_global_decision())
+ replace_table[correction.incorrect] = unistring();
+ break;
+ case splAdd:
+ add_to_dictionary(correction);
+ break;
+
+ case splChoice:
+ case splEdit:
+ if (!replace_with.empty()) {
+ wedit.set_cursor_position(Point(correction.line,
+ correction.offset));
+ wedit.replace_text(replace_with, correction.incorrect.len());
+ if (splwnd.is_global_decision())
+ replace_table[correction.incorrect] = replace_with;
+ // Since we modified the text, the offsets of the
+ // following Correction objects must be adjusted.
+ for (int i = cur_crctn + 1; i < corrections.size(); i++) {
+ if (corrections[i]->offset > correction.offset) {
+ corrections[i]->offset +=
+ replace_with.len() - correction.incorrect.len();
+ }
+ }
+ }
+ break;
+ }
+
+ app.update_terminal();
+ }
+ return true;
+}
+
+// adjust_word_offset() - the speller reports the offsets of incorrect
+// words, but some spellers (like hspell) report incorrect offsets, so
+// we need to detect these cases and find the words ourselves.
+
+void adjust_word_offset(Correction &c, const unistring &str)
+{
+ if (str.index(c.incorrect, c.offset) != c.offset) {
+ // first, search the word near the reported offset
+ int from = c.offset - 10;
+ c.offset = str.index(c.incorrect, (from < 0) ? 0 : from);
+ if (c.offset == -1) {
+ // wasn't found, so search starting from the beginning
+ // of the paragraph.
+ if ((c.offset = str.index(c.incorrect, 0)) == -1)
+ c.offset = 0;
+ }
+ }
+}
+
+// get_word_boundaries() - get the boundaries of the word on which the
+// cursor stands.
+
+void get_word_boundaries(const unistring &str, int cursor, int &wbeg, int &wend)
+{
+ // If the cursor stands just past the word, treat it as if it
+ // stants on the word.
+ if ((cursor == str.len() || !BiDi::is_wordch(str[cursor]))
+ && cursor > 0 && BiDi::is_wordch(str[cursor-1]))
+ cursor--;
+
+ wbeg = wend = cursor;
+
+ if (cursor < str.len() && BiDi::is_wordch(str[cursor])) {
+ while (wbeg > 0 && BiDi::is_wordch(str[wbeg-1]))
+ wbeg--;
+ while (wend < str.len()-1 && BiDi::is_wordch(str[wend+1]))
+ wend++;
+ wend++;
+ }
+}
+
+// erase_special_characters_words() - erases/modifies characters
+// or words that may cause problems to the speller:
+//
+// 0. If we're checking emails and the line is quoted (">"), erase it.
+// 1. remove words with combining characters (e.g. Hebrew points)
+// 2. remove ispell's "\"
+// 3. convert Hebrew maqaf to ASCII one.
+
+void erase_special_characters_words(unistring &str, bool erase_quotes)
+{
+ if (erase_quotes) {
+ // If we're checking emails, erase lines starting
+ // with ">" (with optional preceding spaces).
+ int i = 0;
+ while (i < str.len() && str[i] == ' ')
+ i++;
+ if (i < str.len() && str[i] == '>') {
+ for (i = 0; i < str.len(); i++)
+ str[i] = ' ';
+ }
+ }
+ for (int i = 0; i < str.len(); i++) {
+ if (str[i] == UNI_HEB_MAQAF)
+ str[i] = '-';
+ if (str[i] == '\\') // ispell's line continuation char.
+ str[i] = ' ';
+ }
+ for (int i = 0; i < str.len(); i++) {
+ if (mk_wcwidth(str[i]) == 0) {
+ if (BiDi::is_nsm(str[i])) {
+ // delete the word in which the NSM is.
+ int wbeg, wend;
+ get_word_boundaries(str, i, wbeg, wend);
+ for (int j = wbeg; j < wend; j++)
+ str[j] = ' ';
+ } else {
+ // probably some formatting code (RLM, LRM, etc)
+ str[i] = ' ';
+ }
+ }
+ }
+}
+
+// erase_before_after_word() - erases the text segment preceding or the
+// text segment following the word on which the cursor stands.
+
+void erase_before_after_word(unistring &str, int cursor, bool bef, bool aft)
+{
+ int wbeg, wend;
+ get_word_boundaries(str, cursor, wbeg, wend);
+ if (bef)
+ for (int i = 0; i < wbeg; i++)
+ str[i] = ' ';
+ if (aft) {
+ // but don't erase the hebrew maqaf (ascii-transliterated)
+ if (wend < str.len() && str[wend] == '-')
+ wend++;
+ for (int i = wend; i < str.len(); i++)
+ str[i] = ' ';
+ }
+}
+
+// spell_check() - the principal method.
+
+void Speller::spell_check(splRng range, EditBox &wedit, SpellerWnd &splwnd)
+{
+ if (!is_loaded()) {
+ dialog.show_message(_("Speller is not loaded"));
+ return;
+ }
+
+ bool cancel_spelling = false;
+
+ if (range == splRngWord)
+ write_line("%\n"); // exit terse mode
+ else
+ write_line("!\n"); // enter terse mode
+
+ // Find the start and end paragraphs corresponding to
+ // the requested range.
+ int start_para, end_para;
+ Point cursor_origin;
+ wedit.get_cursor_position(cursor_origin);
+ if (range == splRngAll) {
+ start_para = 0;
+ end_para = wedit.get_number_of_paragraphs() - 1;
+ } else {
+ start_para = cursor_origin.para;
+ if (range == splRngForward)
+ end_para = wedit.get_number_of_paragraphs() - 1;
+ else
+ end_para = start_para;
+ }
+
+ // Some variabls that are used when range==splRngWord
+ bool sole_word_correct = false;
+ unistring sole_word;
+ unistring sole_word_root;
+
+ bool restore_cursor = true;
+
+ for (int i = start_para; i <= end_para && !cancel_spelling; i++)
+ {
+ dialog.show_message_fmt(_("Spell checking... %d/%d"),
+ i+1, wedit.get_number_of_paragraphs());
+ dialog.immediate_update();
+
+ unistring para = wedit.get_paragraph_text(i);
+
+ // erase/modify some characters/words
+ erase_special_characters_words(para,
+ (wedit.get_syn_hlt() == EditBox::synhltEmail) && (range != splRngWord));
+
+ if (i == start_para) {
+ if (range != splRngAll) {
+ // erase text we're not supposed to check.
+ erase_before_after_word(para, cursor_origin.pos,
+ true, range != splRngForward);
+
+ // after finishing checking splRgnForward/splRgnWord,
+ // we restore the cursor to the start of the word on
+ // which it stood.
+ int wbeg, wend;
+ get_word_boundaries(para, cursor_origin.pos, wbeg, wend);
+ cursor_origin.pos = wbeg;
+
+ // also, when checking a sole word, keep it because
+ // we need to display it later in the dialog-line.
+ if (range == splRngWord)
+ sole_word = para.substr(wbeg, wend - wbeg);
+ } else {
+ // after finishing checking the whole document, we
+ // restore cursor position to the first column of
+ // the paragraph.
+ cursor_origin.pos = 0;
+ }
+ }
+
+ // Convert the text to the speller encoding
+ // :TODO: special treatment for UTF-8.
+ cstring cstr;
+ convert_from_unistr(cstr, para, conv_to_speller);
+
+ // Send "^text" to speller
+ cstr.insert(0, "^");
+ cstr += "\n";
+ write_line(cstr.c_str());
+
+ // Read the speller reply, till encountering the empty string,
+ // and construct a Corrections collection.
+ Corrections corrections;
+ Correction *last_corretion = NULL;
+ do {
+ cstr = read_line();
+ if (cstr.size() != 0) {
+ unistring ustr;
+ convert_to_unistr(ustr, cstr, conv_from_speller);
+ Correction *c = new Correction(u8string(ustr).c_str(), i);
+ if (c->is_valid()) {
+ // store the speller-encoded word too, in case
+ // we need to feed it back (like in the "*<<word>>"
+ // command).
+ convert_from_unistr(c->incorrect_original, c->incorrect,
+ conv_to_speller);
+ adjust_word_offset(*c, para);
+ corrections.add(c);
+ last_corretion = c;
+ } else {
+ delete c;
+
+ // Special support for hspell's hints.
+ if ((ustr[0] == ' ' || ustr[0] == 'H') && last_corretion)
+ last_corretion->add_hint(ustr.substr(1));
+
+ // When spell-checking a sole word, we're in
+ // non-terse mode.
+ if (range == splRngWord) {
+ if (ustr[0] == '*' || ustr[0] == '+') {
+ sole_word_correct = true;
+ if (ustr[0] == '+' && ustr.len() > 2)
+ sole_word_root = ustr.substr(2);
+ }
+ }
+ }
+ }
+ } while (cstr.size() != 0);
+
+ corrections.sort();
+
+ // :TODO: adjust UTF-8 offsets.
+
+ if ((cancel_spelling = terminal::was_ctrl_c_pressed()))
+ restore_cursor = false;
+
+ // hand the Corrections collection to the method that interacts
+ // with the user.
+ if (!cancel_spelling && !corrections.empty()) {
+ dialog.show_message_fmt(_("A misspelling was found at %d/%d"),
+ i+1, wedit.get_number_of_paragraphs());
+ cancel_spelling = !interactive_correct(corrections,
+ wedit, splwnd, restore_cursor);
+ }
+ }
+
+ wedit.unset_primary_mark();
+
+ if (restore_cursor && range != splRngWord)
+ wedit.set_cursor_position(cursor_origin);
+
+ if (sole_word_correct) {
+ if (sole_word_root.empty())
+ dialog.show_message_fmt(_("Word '%s' is correct"),
+ u8string(sole_word).c_str());
+ else
+ dialog.show_message_fmt(_("Word '%s' is correct because of %s"),
+ u8string(sole_word).c_str(),
+ u8string(sole_word_root).c_str());
+ } else {
+ dialog.show_message(_("Spell cheking done"));
+ }
+}
+
+// read_line() - read a line from the speller
+
+cstring Speller::read_line()
+{
+ u8string str;
+ char ch;
+ while (read(fd_from_spl[0], &ch, 1)) {
+ if (ch != '\n')
+ str += ch;
+ else
+ break;
+ }
+ return str;
+}
+
+// write_line() - write a line to the speller
+
+void Speller::write_line(const char *s)
+{
+ write(fd_to_spl[1], s, strlen(s));
+}
+
diff --git a/speller.h b/speller.h
new file mode 100644
index 0000000..abc28c4
--- /dev/null
+++ b/speller.h
@@ -0,0 +1,117 @@
+#ifndef BDE_SPELLER_H
+#define BDE_SPELLER_H
+
+#include "editbox.h"
+#include "label.h"
+
+class Editor;
+
+class Correction;
+class Corrections;
+
+// A SpellerWnd object represents the GUI window in which the speller results
+// (the list of suggestions) are displayed, like a menu. Its public interface
+// allows the Speller object to post a menu and get the user's action.
+
+enum MenuResult {
+ splAbort, splAbortRestoreCursor,
+ splIgnore, splAdd, splEdit, splChoice
+};
+
+class SpellerWnd : public Widget {
+
+ Label label;
+ EditBox editbox;
+ Editor &app; // for update_terminal()
+
+ bool finished; // the modal menu executes as long as this
+ // flag is 'true'.
+ void end_menu(MenuResult);
+
+ Correction *correction;
+ unistring word_keys;
+
+ MenuResult menu_result;
+ bool global_decision;
+ int suggestion_choice;
+
+ void clear();
+ void append(const char *s);
+ void append(const unistring &us);
+
+public:
+
+ SpellerWnd(Editor &aApp);
+
+ MenuResult exec_correction_menu(Correction &correction);
+ bool is_global_decision() { return global_decision; }
+ int get_suggestion_choice() { return suggestion_choice; }
+
+ HAS_ACTIONS_MAP(SpellerWnd, Dispatcher);
+ HAS_BINDINGS_MAP(SpellerWnd, Dispatcher);
+
+ INTERACTIVE void ignore_word();
+ INTERACTIVE void edit_replacement();
+ INTERACTIVE void add_to_dict();
+ INTERACTIVE void set_global_decision();
+ INTERACTIVE void abort_spelling();
+ INTERACTIVE void abort_spelling_restore_cursor();
+
+ INTERACTIVE void layout_windows();
+ INTERACTIVE void refresh();
+
+ virtual bool handle_event(const Event &evt);
+ virtual void update();
+ virtual bool is_dirty() const;
+ virtual void invalidate_view();
+ virtual void resize(int lines, int columns, int y, int x);
+ void update_cursor() { editbox.update_cursor(); }
+};
+
+// A Speller object does the communication with the speller. It gets
+// the incorrect words and uses the SpellerWnd object to provide
+// the user with a menu.
+
+class DialogLine;
+class Converter;
+
+class Speller {
+
+ // pipes for communication with the speller process.
+ int fd_to_spl[2];
+ int fd_from_spl[2];
+
+ Editor &app; // for update_terminal()
+ DialogLine &dialog;
+ bool loaded;
+ Converter *conv_to_speller, *conv_from_speller;
+
+ cstring read_line();
+ void write_line(const char *s);
+
+ void add_to_dictionary(Correction &correction);
+
+ bool interactive_correct(Corrections &corrections,
+ EditBox &wedit,
+ SpellerWnd &splwnd,
+ bool &restore_cursor);
+public:
+
+ enum splRng { splRngAll, splRngForward, splRngWord };
+
+ Speller(Editor &app, DialogLine &aDialog);
+
+ bool is_loaded() const { return loaded; }
+
+ bool load(const char *cmd, const char *encoding);
+ void unload();
+
+ void spell_check(splRng range,
+ EditBox &wedit,
+ SpellerWnd &splwnd);
+};
+
+void UNLOAD_SPELLER();
+
+#endif
+
diff --git a/stamp-h.in b/stamp-h.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/stamp-h.in
diff --git a/statusline.cc b/statusline.cc
new file mode 100644
index 0000000..dcc9dc2
--- /dev/null
+++ b/statusline.cc
@@ -0,0 +1,168 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <string.h>
+
+#include "statusline.h"
+#include "editor.h"
+#include "themes.h"
+#include "dbg.h"
+
+StatusLine::StatusLine(const Editor *aBde, EditBox *aEditbox)
+{
+ create_window();
+ aEditbox->set_status_listener(this);
+ editbox = aEditbox;
+ bde = aBde;
+ cursor_position_report = false;
+ update_region = rgnAll;
+}
+
+void StatusLine::resize(int lines, int columns, int y, int x)
+{
+ Widget::resize(lines, columns, y, x);
+ request_update(rgnAll);
+}
+
+void StatusLine::request_update(region rgn)
+{
+ update_region |= rgn;
+}
+
+void StatusLine::invalidate_view()
+{
+ request_update(rgnAll);
+}
+
+void StatusLine::toggle_cursor_position_report()
+{
+ cursor_position_report = !cursor_position_report;
+ request_update(rgnAll);
+}
+
+void StatusLine::update()
+{
+ if (update_region & rgnFilename)
+ request_update(rgnAll);
+
+ if (update_region & rgnAll) {
+ wbkgd(wnd, get_attr(STATUSLINE_ATTR));
+ wmove(wnd, 0, 0);
+ wclrtoeol(wnd);
+ }
+
+ if (update_region & (rgnAll | rgnIndicators)) {
+ wmove(wnd, 0, 0);
+ waddch(wnd, '[');
+
+ waddch(wnd, editbox->is_modified() ? 'M' : '-');
+
+ char wrap_ch = '-';
+ switch (editbox->get_wrap_type()) {
+ case EditBox::wrpOff: wrap_ch = '$'; break;
+ case EditBox::wrpAnywhere: wrap_ch = '\\'; break;
+ case EditBox::wrpAtWhiteSpace: wrap_ch = '-'; break; // silence the compiler
+ }
+ waddch(wnd, wrap_ch);
+
+ char algo_ch = '-';
+ switch (editbox->get_dir_algo()) {
+ case algoUnicode: algo_ch = '!'; break;
+ case algoContextStrong: algo_ch = '~'; break;
+ case algoContextRTL: algo_ch = '-'; break;
+ case algoForceLTR: algo_ch = '>'; break;
+ case algoForceRTL: algo_ch = '<'; break;
+ }
+ waddch(wnd, algo_ch);
+
+ waddch(wnd, editbox->has_selected_text()
+ && !bde->in_spelling() ? '@' : '-');
+
+ waddch(wnd, editbox->is_read_only() ? 'R' : '-');
+
+ waddch(wnd, bde->is_speller_loaded() ? 'S' : '-');
+
+ waddch(wnd, editbox->in_translation_mode() ? '"' : '-');
+
+ waddch(wnd, terminal::do_arabic_shaping ? 'a' : '-');
+
+ waddch(wnd, editbox->get_alt_kbd() ? 'H' : '-');
+
+ waddch(wnd, editbox->is_auto_justify() ? 'j' : '-');
+
+ waddch(wnd, editbox->is_auto_indent() ? 'i' : '-');
+
+ char maqaf_ch = '-';
+ switch (editbox->get_maqaf_display()) {
+ case EditBox::mqfAsis: maqaf_ch = '-'; break;
+ case EditBox::mqfTransliterated: maqaf_ch = 'k'; break;
+ case EditBox::mqfHighlighted: maqaf_ch = 'K'; break;
+ }
+ waddch(wnd, maqaf_ch);
+
+ waddch(wnd, editbox->is_smart_typing() ? 'q' : '-');
+
+ char rtlnsm_ch = '-';
+ switch (editbox->get_rtl_nsm_display()) {
+ case EditBox::rtlnsmOff: rtlnsm_ch = '-'; break;
+ case EditBox::rtlnsmTransliterated: rtlnsm_ch = 'n'; break;
+ case EditBox::rtlnsmAsis: rtlnsm_ch = 'N'; break;
+ }
+ waddch(wnd, rtlnsm_ch);
+
+ waddch(wnd, editbox->get_visual_cursor_movement() ? 'v' : '-');
+
+ waddch(wnd, editbox->has_formatting_marks() ? 'F' : '-');
+
+ waddch(wnd, ']');
+ }
+
+ if (update_region & (rgnAll | rgnFilename)) {
+ unistring tmp;
+ tmp.init_from_filename(bde->get_filename());
+ u8string filename;
+ filename.init_from_unichars(tmp);
+
+ waddch(wnd, ' ');
+ if (filename.empty()) {
+ draw_string(_("UNTITLED"));
+ } else {
+ waddch(wnd, '"');
+ draw_string(filename.c_str());
+ waddch(wnd, '"');
+ }
+ if (bde->is_new())
+ draw_string(_(" [New File]"));
+ wmove(wnd, 0, window_width() - strlen(bde->get_encoding()) - (2+5));
+ wprintw(wnd, "[disk:%s]", bde->get_encoding());
+ }
+
+ if (cursor_position_report && (update_region & (rgnAll | rgnCursorPos))) {
+ // we use 16 columns for the cursor position.
+ int x = window_width() - strlen(bde->get_encoding()) - (2+5) - 16;
+ wmove(wnd, 0, x);
+ Point cursor;
+ editbox->get_cursor_position(cursor);
+ int total = editbox->get_number_of_paragraphs();
+ wprintw(wnd, "%4d/%4d,%3d ", total, cursor.para + 1, cursor.pos + 1);
+ }
+
+ wnoutrefresh(wnd);
+ update_region = rgnNone;
+}
+
diff --git a/statusline.h b/statusline.h
new file mode 100644
index 0000000..d12b64d
--- /dev/null
+++ b/statusline.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_STATUSLINE_H
+#define BDE_STATUSLINE_H
+
+#include "editbox.h"
+
+class Editor;
+
+class StatusLine : public Widget, public EditBoxStatusListener {
+
+ const EditBox *editbox;
+ const Editor *bde;
+
+ enum region { rgnNone = 0, rgnIndicators = 1, rgnFilename = 2,
+ rgnCursorPos = 4, rgnAll = 8 };
+ int update_region;
+
+ bool cursor_position_report; // report cursor position?
+
+public:
+
+ StatusLine(const Editor *aBde, EditBox *aEditbox);
+
+ void toggle_cursor_position_report();
+ bool is_cursor_position_report() const {
+ return cursor_position_report;
+ }
+
+ void request_update(region rgn);
+ virtual void update();
+ virtual void invalidate_view();
+ virtual bool is_dirty() const { return update_region != rgnNone; }
+ virtual void resize(int lines, int columns, int y, int x);
+
+ // the following are inherited from EditBoxStatusListener
+
+ virtual void on_wrap_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_dir_algo_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_selection_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_alt_kbd_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_auto_indent_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_auto_justify_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_modification_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_formatting_marks_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_position_change() {
+ if (cursor_position_report)
+ request_update(rgnCursorPos);
+ }
+
+ virtual void on_read_only_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_smart_typing_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_rtl_nsm_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_maqaf_change() {
+ request_update(rgnIndicators);
+ }
+
+ virtual void on_translation_mode_change() {
+ request_update(rgnIndicators);
+ }
+};
+
+#endif
+
diff --git a/terminal.cc b/terminal.cc
new file mode 100644
index 0000000..89d606a
--- /dev/null
+++ b/terminal.cc
@@ -0,0 +1,213 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+// Baruch says to put editor.h first.
+#include "editor.h" // for SIGHUP's emergency_save
+#include "terminal.h"
+#include "dbg.h"
+
+#include <unistd.h> // _POSIX_VDISABLE
+#include <sys/wait.h> // wait()
+#include <signal.h>
+#include <errno.h>
+#include <termios.h>
+#include <stdlib.h> // getenv
+#include <sys/time.h> // timeval
+#include <string.h> // strstr
+#ifdef HAVE_LANGINFO_CODESET
+# include <langinfo.h>
+#endif
+
+bool terminal::initialized = false;
+bool terminal::is_utf8;
+bool terminal::force_iso88598;
+bool terminal::is_fixed;
+bool terminal::is_color;
+bool terminal::use_default_colors;
+bool terminal::do_arabic_shaping;
+bool terminal::graphical_boxes;
+
+static termios oldterm;
+
+void terminal::finish()
+{
+ endwin();
+ tcsetattr(0, TCSANOW, &oldterm);
+ DBG(1, ("Bailing out\n"));
+}
+
+static RETSIGTYPE sigint_hndlr(int sig)
+{
+}
+
+static RETSIGTYPE sigterm_hndlr(int sig)
+{
+ terminal::finish();
+ exit(0);
+}
+
+static RETSIGTYPE sighup_hndlr(int sig)
+{
+ Editor::get_global_instance()->emergency_save();
+ DBG(1, ("SIGHUP HANDLER\n"));
+ exit(0);
+}
+
+static RETSIGTYPE sigchld_hndlr(int sig)
+{
+ int serrno = errno;
+ wait(NULL);
+ errno = serrno;
+}
+
+// was_ctrl_c_pressed() - is a crude method to check if ^C was pressed
+// while in a non-interactive segment, like when receiving data from
+// the speller. it uses select() to see if there's any keyboard (stdin)
+// input available. If so, it eats it up, and checks whether 0x03 (^C)
+// was encountered.
+
+bool terminal::was_ctrl_c_pressed()
+{
+#define HAVE_SELECT 1
+#ifdef HAVE_SELECT
+ fd_set rfds;
+ timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ bool ctrl_c_pressed = false;
+
+ while (1) {
+ FD_ZERO(&rfds);
+ FD_SET(STDIN_FILENO, &rfds);
+ if (select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv) <= 0)
+ break; // no kbd input avail.
+ char ch;
+ read(STDIN_FILENO, &ch, 1);
+ if (ch == '\x03' || ch == '\x07')
+ ctrl_c_pressed = true;
+ }
+ return ctrl_c_pressed;
+#else
+ return false;
+#endif
+}
+
+// DISABLE_SIGTSTP() is used by child processes (e.g. the speller)
+// to get rid of ncurses' handler. See TODO.
+void DISABLE_SIGTSTP()
+{
+ signal(SIGTSTP, SIG_IGN);
+}
+
+void terminal::init()
+{
+ tcgetattr(0, &oldterm);
+
+#ifdef _POSIX_VDISABLE
+ termios term;
+ term = oldterm;
+ term.c_cc[VINTR] = _POSIX_VDISABLE;
+ term.c_cc[VQUIT] = _POSIX_VDISABLE;
+ term.c_cc[VSTOP] = _POSIX_VDISABLE;
+ term.c_cc[VSTART] = _POSIX_VDISABLE;
+ tcsetattr(0, TCSANOW, &term);
+#else
+ termios term;
+ term = oldterm;
+ term.c_lflag &= ~ISIG;
+ term.c_iflag &= ~(IXON | IXOFF);
+ tcsetattr(0, TCSANOW, &term);
+#endif
+
+ signal(SIGINT, sigint_hndlr);
+ signal(SIGTERM, sigterm_hndlr);
+ signal(SIGHUP, sighup_hndlr);
+ signal(SIGCHLD, sigchld_hndlr);
+
+ // it's important to ignore SIGPIPE because the editor has the ability
+ // to write to a pipe.
+ signal(SIGPIPE, SIG_IGN);
+
+ initscr();
+ keypad(stdscr, TRUE);
+ nonl();
+ cbreak();
+ noecho();
+
+#ifdef HAVE_COLOR
+ if (has_colors()) {
+ is_color = true;
+ start_color();
+#ifdef HAVE_USE_DEFAULT_COLORS
+ terminal::use_default_colors = (::use_default_colors() == OK);
+#else
+ terminal::use_default_colors = false;
+#endif
+ } else {
+ is_color = false;
+ }
+#else
+ is_color = false;
+#endif
+
+ terminal::do_arabic_shaping = false;
+ terminal::initialized = true;
+
+ if (under_x11())
+ terminal::graphical_boxes = true;
+ else
+ terminal::graphical_boxes = false;
+}
+
+bool terminal::is_interactive()
+{
+ return initialized;
+}
+
+// Are we running under X?
+bool terminal::under_x11()
+{
+ return getenv("DISPLAY") && *getenv("DISPLAY");
+}
+
+// determine_locale() - find out the currently used locale.
+
+void terminal::determine_locale()
+{
+ const char *locale;
+ (void) (((locale = getenv("LC_ALL")) && *locale) ||
+ ((locale = getenv("LC_CTYPE")) && *locale) ||
+ ((locale = getenv("LANG"))));
+
+ is_utf8 = false;
+
+#ifdef HAVE_LANGINFO_CODESET
+# define lcl(subs) strstr(nl_langinfo(CODESET), subs)
+#else
+# define lcl(subs) (locale && strstr(locale, subs))
+#endif
+
+ if (lcl("UTF-8") || lcl("utf-8") || lcl("UTF8") || lcl("utf8"))
+ is_utf8 = true;
+
+ // we assume this is a non-fixed terminal if and only if we are in
+ // UTF-8 locale and a DISPLAY environment variable is present (the
+ // user can change this with command-line options).
+ is_fixed = !(is_utf8 && under_x11());
+}
+
diff --git a/terminal.h b/terminal.h
new file mode 100644
index 0000000..ac809eb
--- /dev/null
+++ b/terminal.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_TERMINAL_H
+#define BDE_TERMINAL_H
+
+#include <config.h>
+
+// _XOPEN_SOURCE_EXTENDED is necessary for the wide-curses API
+#ifdef HAVE_WIDE_CURSES
+# define _XOPEN_SOURCE_EXTENDED 1
+#endif
+
+#if defined(HAVE_NCURSESW_NCURSES_H)
+# include <ncursesw/ncurses.h>
+#elif defined(HAVE_NCURSES_H)
+# include <ncurses.h>
+#else
+# include <curses.h>
+#endif
+
+// :TODO: move this gettext() stuff to a more relevant header?
+#ifdef USE_GETTEXT
+# include <libintl.h>
+# define _(String) gettext(String)
+# define gettext_noop(String) String
+# define N_(String) gettext_noop(String)
+#else
+# define _(String) String
+# define N_(String) String
+# define textdomain(Domain)
+# define bindtextdomain(Package, Directory)
+#endif
+
+class terminal {
+private:
+ static bool initialized;
+
+public:
+ static bool is_utf8; // Are we in UTF-8 locale?
+
+ static bool force_iso88598; // Assume this is an ISO-8859-8 terminal?
+
+ static bool is_fixed; // Do we use a "fixed" terminal? a terminal
+ // which is not capable of displaying wide-
+ // characetrs and combining characters.
+
+ static bool is_color; // Does our terminal support colors?
+
+ static bool use_default_colors;// ncurses' use_default_colors() used?
+
+ static bool do_arabic_shaping; // instruct all widgets to do arabic shaping.
+
+ static bool graphical_boxes; // Use graphical chars for the menu, scrollbar.
+
+ static void init();
+ static void finish();
+ static bool was_ctrl_c_pressed();
+ static bool is_interactive();
+ static void determine_locale();
+ static bool under_x11();
+};
+
+void DISABLE_SIGTSTP();
+
+#endif
+
diff --git a/themes.cc b/themes.cc
new file mode 100644
index 0000000..50d357b
--- /dev/null
+++ b/themes.cc
@@ -0,0 +1,564 @@
+#include <config.h>
+
+#include "io.h" // get_cfg_filename
+#include "pathnames.h"
+#include "widget.h"
+#include "themes.h"
+
+#include <errno.h>
+
+#include <map>
+
+#ifdef HAVE_COLOR
+
+#define MISSING_COLOR -600
+#define NO_COLOR -601
+#define SKIP_COLOR -602
+
+// An ATTR structure describes the attributes (color, etc) of
+// a single GUI element.
+
+struct ATTR {
+ int ident;
+ char *name; // the name of the GUI element in the theme file
+
+ // These two members point to a parent ATTR struct
+ // from which the foreground & background colors will
+ // be read if they are missing in the theme file.
+ int fg_parent;
+ int bg_parent;
+
+ int fg; // holds color number
+ int bg; // "
+ int extra; // A_BOLD, A_UNDERLINE, etc...
+ int pair; // init_pair(fg, bg)
+
+ void clear()
+ {
+ // clear only the properties that are read from disk,
+ // not those initialized in this file (because the
+ // latter are unchangeable).
+ fg = bg = MISSING_COLOR;
+ extra = 0;
+ pair = 0;
+ }
+};
+
+ATTR attr_table[] = {
+ { MENUBAR_ATTR, "menubar", MENU_ATTR, MENU_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_ATTR, "menu", MENU_ATTR, MENU_ATTR,
+ -1, -1, 0, 0 },
+ { MENU_SELECTED_ATTR, "menu.selected", MENU_ATTR, MENU_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_FRAME_ATTR, "menu.frame", MENU_ATTR, MENU_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_LETTER_ATTR, "menu.letter", MENU_ATTR, MENU_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_LETTER_SELECTED_ATTR, "menu.letter.selected", MENU_LETTER_ATTR, MENU_SELECTED_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_INDICATOR_ATTR, "menu.indicator", MENU_ATTR, MENU_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { MENU_INDICATOR_SELECTED_ATTR, "menu.indicator.selected", MENU_INDICATOR_ATTR, MENU_SELECTED_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { STATUSLINE_ATTR, "statusline", MENUBAR_ATTR, MENUBAR_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_ATTR, "edit", EDIT_ATTR, EDIT_ATTR,
+ -1, -1, 0, 0 },
+ { DIALOGLINE_ATTR, "dialogline", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { SCROLLBAR_ATTR, "scrollbar", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { SCROLLBAR_THUMB_ATTR, "scrollbar.thumb", MENUBAR_ATTR, MENUBAR_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_FAILED_CONV_ATTR, "edit.failed-conversion", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_CONTROL_ATTR, "edit.control", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EOP_ATTR, "edit.eop", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EXPLICIT_ATTR, "edit.explicit-bidi", EDIT_EOP_ATTR, EDIT_EOP_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_NSM_ATTR, "edit.nsm", EDIT_EXPLICIT_ATTR, EDIT_EXPLICIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_NSM_HEBREW_ATTR, "edit.nsm.hebrew", EDIT_NSM_ATTR, EDIT_NSM_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_NSM_CANTILLATION_ATTR, "edit.nsm.cantillation", EDIT_NSM_HEBREW_ATTR, EDIT_NSM_HEBREW_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_NSM_ARABIC_ATTR, "edit.nsm.arabic", EDIT_NSM_ATTR, EDIT_NSM_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_TAB_ATTR, "edit.tab", EDIT_EOP_ATTR, EDIT_EOP_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_WIDE_ATTR, "edit.wide", EDIT_NSM_ATTR, EDIT_NSM_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_TRIM_ATTR, "edit.trim", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_WRAP_ATTR, "edit.wrap", EDIT_TRIM_ATTR, EDIT_TRIM_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_MAQAF_ATTR, "edit.maqaf", EDIT_EXPLICIT_ATTR, EDIT_EXPLICIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_UNICODE_LS_ATTR, "edit.unicode-ls", EDIT_EOP_ATTR, EDIT_EOP_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_SELECTED_ATTR, "edit.selected", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_HTML_TAG_ATTR, "edit.html-tag", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMPHASIZED_ATTR, "edit.emphasized", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_LINKS_ATTR, "edit.links", EDIT_EMPHASIZED_ATTR, EDIT_EMPHASIZED_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE1_ATTR, "edit.email-quote1", EDIT_ATTR, EDIT_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE2_ATTR, "edit.email-quote2", EDIT_EMAIL_QUOTE1_ATTR, EDIT_EMAIL_QUOTE1_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE3_ATTR, "edit.email-quote3", EDIT_EMAIL_QUOTE2_ATTR, EDIT_EMAIL_QUOTE2_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE4_ATTR, "edit.email-quote4", EDIT_EMAIL_QUOTE3_ATTR, EDIT_EMAIL_QUOTE3_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE5_ATTR, "edit.email-quote5", EDIT_EMAIL_QUOTE4_ATTR, EDIT_EMAIL_QUOTE4_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE6_ATTR, "edit.email-quote6", EDIT_EMAIL_QUOTE5_ATTR, EDIT_EMAIL_QUOTE5_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE7_ATTR, "edit.email-quote7", EDIT_EMAIL_QUOTE6_ATTR, EDIT_EMAIL_QUOTE6_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE8_ATTR, "edit.email-quote8", EDIT_EMAIL_QUOTE7_ATTR, EDIT_EMAIL_QUOTE7_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 },
+ { EDIT_EMAIL_QUOTE9_ATTR, "edit.email-quote9", EDIT_EMAIL_QUOTE8_ATTR, EDIT_EMAIL_QUOTE8_ATTR,
+ MISSING_COLOR, MISSING_COLOR, 0, 0 }
+};
+
+#define ARRAY_SIZE(nm) (int)(sizeof(nm)/sizeof(nm[0]))
+
+static const char *default_color_theme[] =
+{
+"menu = white, cyan",
+"menu.frame = black",
+"menu.selected = , black",
+"menu.letter = yellow",
+"menu.indicator = red",
+"menubar = black",
+"edit = default, default",
+"edit.eop = red",
+"edit.tab = red",
+"edit.explicit-bidi = magenta",
+"edit.maqaf = brightmagenta",
+"edit.nsm = brightmagenta",
+"edit.nsm.hebrew = green",
+"edit.nsm.cantillation = blue",
+"edit.nsm.arabic = brown",
+"edit.unicode-ls = green",
+"edit.wide = brightmagenta",
+"edit.control = yellow",
+"edit.failed-conversion = brightmagenta",
+"edit.trim = bold",
+"edit.wrap = bold",
+"edit.selected = reverse, +bold",
+"edit.html-tag = bold",
+"edit.email-quote1 = bold",
+"edit.emphasized = underline, +bold",
+NULL
+};
+
+static const char *default_bw_theme[] =
+{
+"menu = reverse",
+"menu.selected = normal",
+"menu.letter = normal",
+"menu.indicator = reverse",
+"menu.indicator.selected = normal",
+"edit = normal",
+"edit.eop = normal",
+"edit.tab = normal",
+"edit.explicit-bidi = bold",
+"edit.maqaf = bold",
+"edit.nsm = bold",
+"edit.nsm.hebrew = bold",
+"edit.nsm.cantillation = bold",
+"edit.nsm.arabic = bold",
+"edit.unicode-ls = bold",
+"edit.wide = bold",
+"edit.control = bold",
+"edit.failed-conversion = bold",
+"edit.trim = bold",
+"edit.wrap = bold",
+"edit.selected = reverse",
+"edit.html-tag = bold",
+"edit.email-quote1 = bold",
+"edit.emphasized = underline, +bold",
+NULL
+};
+
+static u8string theme_name;
+
+const char *get_theme_name()
+{
+ return theme_name.c_str();
+}
+
+static ATTR *get_attr_ent(int ident)
+{
+ static std::map<int, ATTR *> hash;
+
+ if (hash.empty())
+ for (int i = 0; i < ARRAY_SIZE(attr_table); i++)
+ hash[attr_table[i].ident] = &attr_table[i];
+
+ std::map<int, ATTR *>::const_iterator it = hash.find(ident);
+ if (it != hash.end())
+ return it->second;
+ else
+ return NULL;
+}
+
+int get_attr(int ident)
+{
+ ATTR *attr = get_attr_ent(ident);
+ // ASSERT(attr != NULL);
+ if (attr->pair)
+ return COLOR_PAIR(attr->pair) | attr->extra;
+ else
+ return attr->extra;
+}
+
+static int get_name_ident(const char *name)
+{
+ for (int i = 0; i < ARRAY_SIZE(attr_table); i++) {
+ if (STREQ(attr_table[i].name, name))
+ return attr_table[i].ident;
+ }
+ return -1;
+}
+
+// parse_attr() parses a "color" name and puts the
+// appropriate values in an ATTR struct.
+
+static bool parse_attr(const char *s, ATTR *attr, bool is_fg)
+{
+ static struct {
+ const char *name;
+ int color;
+ int extra;
+ } names_arr[] = {
+ { "black", COLOR_BLACK, 0 },
+ { "gray", COLOR_BLACK, A_BOLD },
+ { "grey", COLOR_BLACK, A_BOLD },
+ { "red", COLOR_RED, 0 },
+ { "brightred", COLOR_RED, A_BOLD },
+ { "green", COLOR_GREEN, 0 },
+ { "brightgreen", COLOR_GREEN, A_BOLD },
+ { "brown", COLOR_YELLOW, 0 },
+ { "yellow", COLOR_YELLOW, A_BOLD },
+ { "blue", COLOR_BLUE, 0 },
+ { "brightblue", COLOR_BLUE, A_BOLD },
+ { "magenta", COLOR_MAGENTA, 0 },
+ { "brightmagenta", COLOR_MAGENTA, A_BOLD },
+ { "cyan", COLOR_CYAN, 0 },
+ { "brightcyan", COLOR_CYAN, A_BOLD },
+ { "lightgray", COLOR_WHITE, 0 },
+ { "lightgrey", COLOR_WHITE, 0 },
+ { "white", COLOR_WHITE, A_BOLD },
+
+ { "default", -1, 0 },
+
+ { "", MISSING_COLOR, 0 },
+ { "inherit", MISSING_COLOR, 0 },
+
+ { "bold", NO_COLOR, A_BOLD },
+ { "underline", NO_COLOR, A_UNDERLINE },
+ { "reverse", NO_COLOR, A_REVERSE },
+ { "normal", NO_COLOR, 0 },
+
+ { "+bold", SKIP_COLOR, A_BOLD },
+ { "+underline", SKIP_COLOR, A_UNDERLINE },
+ { "+reverse", SKIP_COLOR, A_REVERSE }
+ };
+
+ bool found = false;
+ for (int i = 0; i < ARRAY_SIZE(names_arr) && !found; i++) {
+ if (STREQ(s, names_arr[i].name)) {
+ if (names_arr[i].color != SKIP_COLOR) {
+ if (is_fg)
+ attr->fg = names_arr[i].color;
+ else
+ attr->bg = names_arr[i].color;
+ }
+ attr->extra |= names_arr[i].extra;
+ found = true;
+ }
+ }
+ return found;
+}
+
+#define LINE_TYPE_NONE 0
+#define LINE_TYPE_ATTR 1
+
+static bool parse_line(char *ln, int *line_type, ATTR *attr)
+{
+ if (strchr(ln, '#'))
+ *strchr(ln, '#') = '\0';
+
+ if (strchr(ln, '=')) {
+ *line_type = LINE_TYPE_ATTR;
+ attr->clear();
+ char *pos = strchr(ln, '=');
+ u8string name = u8string(ln, pos).trim();
+ if ((attr->ident = get_name_ident(name.c_str())) == -1)
+ return false;
+ ln = ++pos;
+ int token_no = 0;
+ while (pos) {
+ u8string token;
+ pos = strchr(ln, ',');
+ if (!pos)
+ token = u8string(ln);
+ else
+ token = u8string(ln, pos);
+ token = token.trim();
+ if (!parse_attr(token.c_str(), attr, token_no == 0))
+ return false;
+ ln = pos + 1;
+ token_no++;
+ }
+ } else {
+ *line_type = LINE_TYPE_NONE;
+ }
+
+ return true;
+}
+
+static void clear_table()
+{
+ for (int i = 0; i < ARRAY_SIZE(attr_table); i++)
+ attr_table[i].clear();
+}
+
+// complete_table() is called after the theme file has been parsed
+// and attr_table populated. Its function is to get rid of any
+// missing colors - by inheritance from the parent elements.
+//
+// Also, if our curses implementation doesn't support the use of
+// default fg & bg colors, we use white & black instead.
+
+static void complete_table()
+{
+ for (int i = 0; i < ARRAY_SIZE(attr_table); i++) {
+ if (attr_table[i].fg == MISSING_COLOR) {
+ attr_table[i].fg = get_attr_ent(attr_table[i].fg_parent)->fg;
+ attr_table[i].extra |= get_attr_ent(attr_table[i].fg_parent)->extra;
+ }
+ if (attr_table[i].bg == MISSING_COLOR) {
+ attr_table[i].bg = get_attr_ent(attr_table[i].bg_parent)->bg;
+ // No sense in A_BOLD for background.
+ //attr_table[i].extra |= get_attr_ent(attr_table[i].bg_parent)->extra;
+ }
+ if (!terminal::use_default_colors) {
+ if (attr_table[i].fg == -1)
+ attr_table[i].fg = COLOR_WHITE;
+ if (attr_table[i].bg == -1)
+ attr_table[i].bg = COLOR_BLACK;
+ }
+ }
+}
+
+// allocate_color_pairs() is called after the theme file
+// has been read and parsed. It does the actual color
+// allocation, by calling init_pait().
+
+static bool allocate_color_pairs(ThemeError &theme_error)
+{
+ int clrpr = 1; // color pair '0' is reserved and cannot be redefined.
+ for (int i = 0; i < ARRAY_SIZE(attr_table); i++) {
+ if (attr_table[i].fg == NO_COLOR || attr_table[i].bg == NO_COLOR) {
+ attr_table[i].pair = 0;
+ } else {
+ if (!terminal::is_color) {
+ theme_error.what = ThemeError::errNoColorTerminal;
+ return false;
+ }
+ bool already_allocated = false;
+ for (int j = 0; j < i; j++) {
+ if (attr_table[j].fg == attr_table[i].fg
+ && attr_table[j].bg == attr_table[i].bg) {
+ attr_table[i].pair = attr_table[j].pair;
+ already_allocated = true;
+ break;
+ }
+ }
+ if (!already_allocated) {
+ if (clrpr > COLOR_PAIRS-1) {
+ theme_error.what = ThemeError::errNotEnoughColorPairs;
+ return false;
+ }
+ init_pair(clrpr, attr_table[i].fg, attr_table[i].bg);
+ attr_table[i].pair = clrpr;
+ clrpr++;
+ }
+ }
+ }
+ return true;
+}
+
+bool load_theme(const char *basefilename, ThemeError &theme_error)
+{
+#define MAX_LINE_LEN 1024
+ FILE *fp;
+ u8string filename;
+ theme_error.what = ThemeError::errNone;
+
+ // First, try to load from the user directory.
+ filename.cformat("%s%s", get_cfg_filename(USER_THEMES_DIR), basefilename);
+ if ((fp = fopen(filename.c_str(), "r")) == NULL) {
+ if (errno != ENOENT) {
+ // File exists, but some IO error occured.
+ theme_error.what = ThemeError::errIO;
+ theme_error.sys_errno = errno;
+ theme_error.filename = filename;
+ return false;
+ }
+ // Now try to load from the system directory.
+ filename.cformat("%s%s", get_cfg_filename(SYSTEM_THEMES_DIR), basefilename);
+ if ((fp = fopen(filename.c_str(), "r")) == NULL) {
+ theme_error.what = ((errno == ENOENT) ? ThemeError::errNotFound : ThemeError::errIO);
+ theme_error.sys_errno = errno;
+ theme_error.filename = filename;
+ return false;
+ }
+ }
+ theme_error.filename = filename; // for possible future errors.
+
+ clear_table();
+
+ char line[MAX_LINE_LEN];
+ ATTR prs_attr;
+ int line_type;
+ int line_no = 1;
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ if (!parse_line(line, &line_type, &prs_attr)) {
+ theme_error.what = ThemeError::errSyntax;
+ theme_error.line_no = line_no;
+ return false;
+ } else if (line_type == LINE_TYPE_ATTR) {
+ ATTR *attr = get_attr_ent(prs_attr.ident);
+ attr->fg = prs_attr.fg;
+ attr->bg = prs_attr.bg;
+ attr->extra = prs_attr.extra;
+ }
+ line_no++;
+ }
+ fclose(fp);
+
+ complete_table();
+ if (allocate_color_pairs(theme_error)) {
+ theme_name = basefilename;
+ return true;
+ } else {
+ return false;
+ }
+#undef MAX_LINE_LEN
+}
+
+static bool load_default_theme(const char *theme_file_name,
+ const char **memory_theme,
+ ThemeError &theme_error)
+{
+ // First, try to load the theme from disk.
+ if (!load_theme(theme_file_name, theme_error)) {
+ if (theme_error.what != ThemeError::errNotFound)
+ // The theme is found on disk, be could not
+ // be processed.
+ return false;
+ } else {
+ return true;
+ }
+
+ // No theme found on disk. Load from memory.
+
+ clear_table();
+
+ ATTR prs_attr;
+ int line_type;
+ int line_no = 1;
+ while (*memory_theme) {
+ char line[1000];
+ strcpy(line, *memory_theme);
+ if (!parse_line(line, &line_type, &prs_attr)) {
+ theme_error.what = ThemeError::errSyntax;
+ theme_error.filename = "-INTERNAL-";
+ theme_error.line_no = line_no;
+ return false;
+ } else if (line_type == LINE_TYPE_ATTR) {
+ ATTR *attr = get_attr_ent(prs_attr.ident);
+ attr->fg = prs_attr.fg;
+ attr->bg = prs_attr.bg;
+ attr->extra = prs_attr.extra;
+ }
+ memory_theme++;
+ line_no++;
+ }
+
+ complete_table();
+ allocate_color_pairs(theme_error);
+ theme_name = theme_file_name;
+ return true;
+}
+
+bool load_default_theme(ThemeError &theme_error)
+{
+ return load_default_theme(
+ terminal::is_color ? "default.thm" : "default_bw.thm",
+ terminal::is_color ? default_color_theme : default_bw_theme,
+ theme_error);
+}
+
+#else
+
+// Some stub functions for VERY primitive curses implementations:
+
+int get_attr(int /*ident*/)
+{
+ return A_NORMAL;
+}
+
+bool load_theme(const char * /*basefilename*/, ThemeError &/*theme_error*/)
+{
+ return true;
+}
+
+bool load_default_theme(ThemeError &/*theme_error*/)
+{
+ return true;
+}
+
+const char *get_theme_name()
+{
+ return "";
+}
+
+#endif // HAVE_COLOR
+
+u8string ThemeError::format() const
+{
+ u8string ret;
+ switch (what) {
+ case errSyntax:
+ ret.cformat("Syntax error at line %d of %s", line_no, filename.c_str());
+ break;
+ case errNoColorTerminal:
+ ret.cformat("Your terminal does not support colors, so I can't use theme %s", filename.c_str());
+ break;
+ case errNotEnoughColorPairs:
+ ret.cformat("Your terminal support only %d color pairs, so I can't use theme %s", COLOR_PAIRS, filename.c_str());
+ break;
+ case errNotFound:
+ ret.cformat("File does not exist: %s", filename.c_str());
+ break;
+ case errIO:
+ ret.cformat("Can't open file %s: %s", filename.c_str(), strerror(sys_errno));
+ break;
+ case errNone:
+ // silence the compiler
+ break;
+ }
+ return ret;
+}
+
diff --git a/themes.h b/themes.h
new file mode 100644
index 0000000..f151da5
--- /dev/null
+++ b/themes.h
@@ -0,0 +1,69 @@
+#ifndef BDE_THEMES_H
+#define BDE_THEMES_H
+
+#include <config.h>
+
+#define MENUBAR_ATTR 10
+#define MENU_ATTR 11
+#define MENU_SELECTED_ATTR 12
+#define MENU_FRAME_ATTR 13
+#define MENU_LETTER_ATTR 14
+#define MENU_LETTER_SELECTED_ATTR 15
+#define MENU_INDICATOR_ATTR 16
+#define MENU_INDICATOR_SELECTED_ATTR 17
+#define STATUSLINE_ATTR 18
+#define EDIT_ATTR 19
+#define DIALOGLINE_ATTR 20
+#define SCROLLBAR_ATTR 21
+#define SCROLLBAR_THUMB_ATTR 22
+#define EDIT_FAILED_CONV_ATTR 23
+#define EDIT_CONTROL_ATTR 24
+#define EDIT_EOP_ATTR 25
+#define EDIT_EXPLICIT_ATTR 26
+#define EDIT_NSM_ATTR 27
+#define EDIT_NSM_HEBREW_ATTR 28
+#define EDIT_NSM_CANTILLATION_ATTR 29
+#define EDIT_NSM_ARABIC_ATTR 30
+#define EDIT_TAB_ATTR 31
+#define EDIT_WIDE_ATTR 32
+#define EDIT_TRIM_ATTR 33
+#define EDIT_WRAP_ATTR 34
+#define EDIT_MAQAF_ATTR 35
+#define EDIT_UNICODE_LS_ATTR 36
+#define EDIT_SELECTED_ATTR 37
+#define EDIT_HTML_TAG_ATTR 38
+#define EDIT_EMPHASIZED_ATTR 39
+#define EDIT_LINKS_ATTR 40
+#define EDIT_EMAIL_QUOTE1_ATTR 101
+#define EDIT_EMAIL_QUOTE2_ATTR 102
+#define EDIT_EMAIL_QUOTE3_ATTR 103
+#define EDIT_EMAIL_QUOTE4_ATTR 104
+#define EDIT_EMAIL_QUOTE5_ATTR 105
+#define EDIT_EMAIL_QUOTE6_ATTR 106
+#define EDIT_EMAIL_QUOTE7_ATTR 107
+#define EDIT_EMAIL_QUOTE8_ATTR 108
+#define EDIT_EMAIL_QUOTE9_ATTR 109
+
+struct ThemeError {
+ enum { errNone, errNotFound, errIO,
+ errSyntax, errNoColorTerminal, errNotEnoughColorPairs } what;
+ int sys_errno; // for errIO
+ int line_no; // for errSytax
+ u8string filename;
+ u8string format() const;
+};
+
+int get_attr(int ident);
+
+#ifdef HAVE_COLOR
+# define contains_color(attr) PAIR_NUMBER(attr)
+#else
+# define contains_color(attr) 0
+#endif
+
+bool load_theme(const char *basefilename, ThemeError &theme_error);
+bool load_default_theme(ThemeError &theme_error);
+const char *get_theme_name();
+
+#endif
+
diff --git a/themes/README.themes b/themes/README.themes
new file mode 100644
index 0000000..a64b7f0
--- /dev/null
+++ b/themes/README.themes
@@ -0,0 +1,86 @@
+THE FORMAT OF THEME FILES
+-------------------------
+
+A theme is a collection of attributes of various UI elements. Each
+theme is contained in a file with a "thm" extension.
+
+Each line of a theme file, unless blank or preceded with "#" (which
+makes it a comment), is of the syntax:
+
+ element_name = [fg_color | special] [, bg_color | special] [, special]
+
+"color" may be any of:
+
+ black gray
+ red brightred
+ green brightgreen
+ brown yellow
+ blue brightblue
+ magenta brighmagenta
+ cyan brightcyan
+ lightgray white
+
+(Because of terminal restrictions, those in the second column cannot
+serve as background colors).
+
+"special" may be any of
+
+ bold +bold
+ underline +underline
+ reverse +reverse
+ normal
+ default
+
+"default" stands for the default color of the terminal. This can be
+either the foreground or the background color, depending on where it's
+used.
+
+There are tens of elements, but thanks to an inheritance mechanism it's
+only necessary to specify few of them. Those elements not specified in a
+theme file inherit their attributes from a predetermined "parent"
+element. This inheritance also takes place when only a background (or a
+foreground) color is missing.
+
+The following is a list of the available UI elements and their descriptions.
+The indentations show the hierarchical structure of the inheritance.
+
+menu pull-down menu color
+ menu.frame menu frames
+ menu.selected selected menu item
+ menu.letter item hot key
+ menu.letter.selected (bg: menu.selected) item hot key, when selected
+ menu.indicator menu indicators
+ menu.indicator.selected (bg: menu.selected) menu indicators, selected
+ menubar menubar
+ scrollbar.thumb the thumb of the scrollbar
+ statusline the status-line
+
+edit the main edit box
+ dialogline the dialog-line
+ scrollbar the scrollbar
+ edit.failed-conversion characters not represented
+ in screen encoding.
+ edit.control control characters
+ (usually U+0000 to U+0031)
+ edit.eop end of paragraph symbol
+ edit.unicode-ls Unicode line separator
+ edit.tab tab characters
+ edit.explicit-bidi explicit bidi marks
+ edit.maqaf Hebrew maqaf, when highlighted
+ edit.nsm non spacing marks (NSM)
+ edit.nsm.hebrew Hebrew NSM (niqqud)
+ edit.nsm.cantillation cantillation marks
+ edit.nsm.arabic Arabic NSM (harakat)
+ edit.wide wide Asian characters
+ edit.trim the trim symbol ("$")
+ edit.wrap the wrap symbol ("\")
+ edit.selected selected text
+ edit.html-tag highlighted HTML tags
+ edit.emphasized highlighted *text* and _text_
+ edit.links links in the User Manual
+ edit.email-quote1 email quotes, level 1
+ edit.email-quote2 email quotes, level 2
+ edit.email-quote3 email quotes, level 3
+ ... ...
+ edit.email-quote9 email quotes, level 9
+
diff --git a/themes/arnold.thm b/themes/arnold.thm
new file mode 100644
index 0000000..4804930
--- /dev/null
+++ b/themes/arnold.thm
@@ -0,0 +1,43 @@
+#
+# Title: ~Arnold's "girlie men"
+# Version: 0.6.1
+#
+
+menu = white, blue
+# uncomment the following if you don't like bold text
+#menu = lightgray, blue
+menu.frame = yellow
+menu.frame = magenta
+menu.selected = , black
+menu.selected = white, cyan
+menu.letter = yellow
+menu.indicator = brightred
+
+edit = white, magenta # chg
+
+# uncomment the following if you don't like bold text
+#edit = lightgray, magenta
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = magenta
+edit.maqaf = yellow # chg
+edit.nsm = brightmagenta
+edit.nsm.hebrew = green
+edit.nsm.cantillation = blue
+edit.nsm.arabic = brown
+edit.unicode-ls = green
+edit.wide = brightmagenta
+edit.control = yellow
+edit.failed-conversion = brightmagenta
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = black, cyan # chg
+edit.html-tag = brightgreen # chg
+edit.email-quote1 = brightgreen # chg
+edit.email-quote2 = yellow # chg
+edit.email-quote3 = brightcyan # chg
+edit.email-quote4 = brightblue # chg
+
+edit.emphasized = yellow, +underline # chg
diff --git a/themes/borland.thm b/themes/borland.thm
new file mode 100644
index 0000000..e07a0a5
--- /dev/null
+++ b/themes/borland.thm
@@ -0,0 +1,34 @@
+#
+# Title: Borland ~IDE's menus (transparent)
+# Version: 0.6.1
+#
+
+menu = black, lightgray # chg
+menu.selected = , green # chg
+menu.letter = red # chg
+menu.indicator = red # chg
+
+edit = default, default
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = magenta
+edit.maqaf = brightmagenta
+edit.nsm = brightmagenta
+edit.nsm.hebrew = green
+edit.nsm.cantillation = blue
+edit.nsm.arabic = brown
+edit.unicode-ls = green
+edit.wide = brightmagenta
+edit.control = yellow
+edit.failed-conversion = brightmagenta
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = reverse, +bold
+edit.html-tag = bold
+edit.email-quote1 = bold
+# Note: in the following we use 'bold' too, because the
+# console can't show underline.
+edit.emphasized = underline, +bold
+
diff --git a/themes/default.thm b/themes/default.thm
new file mode 100644
index 0000000..fbec452
--- /dev/null
+++ b/themes/default.thm
@@ -0,0 +1,37 @@
+#
+# Title: Default (transparent)
+# Version: 0.6.1
+#
+# This is the default theme for color terminals.
+#
+
+menu = white, cyan
+menu.frame = black
+menu.selected = , black
+menu.letter = yellow
+menu.indicator = red
+menubar = black
+
+edit = default, default
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = magenta
+edit.maqaf = brightmagenta
+edit.nsm = brightmagenta
+edit.nsm.hebrew = green
+edit.nsm.cantillation = blue
+edit.nsm.arabic = brown
+edit.unicode-ls = green
+edit.wide = brightmagenta
+edit.control = yellow
+edit.failed-conversion = brightmagenta
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = reverse, +bold
+edit.html-tag = bold
+edit.email-quote1 = bold
+# Note: in the following we use 'bold' too, because the
+# console can't show underline.
+edit.emphasized = underline, +bold
diff --git a/themes/default_bw.thm b/themes/default_bw.thm
new file mode 100644
index 0000000..2e315d3
--- /dev/null
+++ b/themes/default_bw.thm
@@ -0,0 +1,40 @@
+#
+# Title: ~Black & White (transparent)
+# Version: 0.6.1
+#
+# This is the default theme for terminals that don't support colors.
+#
+# ("Black & White" is a misnomer, because there aren't any colors here.
+# It's all transparent.)
+#
+# PLEASE DON'T USE COLORS IN THIS FILE (NOT EVEN "BLACK" AND "WHITE") OR
+# ELSE GERESH WILL FAIL TO LOAD WHEN YOU RUN IT ON NON-COLOR TERMINALS.
+#
+
+menu = reverse
+menu.selected = normal
+menu.letter = normal
+menu.indicator = reverse
+menu.indicator.selected = normal
+
+edit = normal
+
+edit.eop = normal
+edit.tab = normal
+edit.explicit-bidi = bold
+edit.maqaf = bold
+edit.nsm = bold
+edit.nsm.hebrew = bold
+edit.nsm.cantillation = bold
+edit.nsm.arabic = bold
+edit.unicode-ls = bold
+edit.wide = bold
+edit.control = bold
+edit.failed-conversion = bold
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = reverse
+edit.html-tag = bold
+edit.email-quote1 = bold
+edit.emphasized = underline, +bold
diff --git a/themes/green.thm b/themes/green.thm
new file mode 100644
index 0000000..8226659
--- /dev/null
+++ b/themes/green.thm
@@ -0,0 +1,34 @@
+#
+# Title: Old ~green monitor
+# Version: 0.6.1
+#
+
+menu = green, black
+menu.selected = black, green
+menu.letter = brightgreen
+menu.letter.selected = gray
+menu.indicator = brightgreen
+menu.indicator.selected = gray
+menubar = brightgreen
+
+edit = green, black
+
+scrollbar.thumb = black, green
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = bold
+edit.maqaf = bold
+edit.nsm = bold
+edit.control = bold
+edit.failed-conversion = brightred
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = reverse, +bold
+edit.html-tag = bold
+edit.email-quote1 = bold
+# Note: in the following we use 'bold' too, because the
+# console can't show underline.
+edit.emphasized = underline, +bold
+
diff --git a/themes/mc.thm b/themes/mc.thm
new file mode 100644
index 0000000..567a9dd
--- /dev/null
+++ b/themes/mc.thm
@@ -0,0 +1,39 @@
+#
+# Title: ~Midnight Commander
+# Version: 0.6.1
+#
+
+menu = white, cyan
+menu.frame = black
+menu.selected = , black
+menu.letter = yellow
+menu.indicator = red
+menubar = black
+
+edit = lightgray, blue # chg
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = magenta
+edit.maqaf = brightmagenta
+edit.nsm = brightmagenta
+edit.nsm.hebrew = green
+edit.nsm.cantillation = blue
+edit.nsm.arabic = brown
+edit.unicode-ls = green
+edit.wide = brightmagenta
+edit.control = yellow
+edit.failed-conversion = brightmagenta
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = black, cyan # chg
+edit.html-tag = brightgreen # chg
+edit.email-quote1 = green # chg
+edit.email-quote2 = brightred # chg
+edit.email-quote3 = cyan # chg
+edit.email-quote4 = brown # chg
+
+# Note: in the following we use 'bold' too, because the
+# console can't show underline.
+edit.emphasized = underline, +bold
diff --git a/themes/occult.thm b/themes/occult.thm
new file mode 100644
index 0000000..779d1ee
--- /dev/null
+++ b/themes/occult.thm
@@ -0,0 +1,50 @@
+#
+# Title: ~Occult (transparent)
+# Version: 0.6.1
+#
+# This theme might look cool with some marble picture at the background.
+#
+
+menu = red, black
+menu.frame = brightred
+menu.selected = brightred, black
+menu.letter = yellow
+menu.indicator = red
+statusline = brightred, black
+dialogline = red, black
+
+edit = default, default
+
+scrollbar = red, black
+scrollbar.thumb = black, red
+
+# uncomment the following lines if you dont like reddish scrollbar.
+#scrollbar = ,
+#scrollbar.thumb = reverse
+
+# If you're using some background picture, you might want to uncomment
+# the following to make the text more readable.
+#edit = white, default
+
+edit.eop = red
+edit.tab = red
+edit.explicit-bidi = magenta
+edit.maqaf = brightmagenta
+edit.nsm = brightmagenta
+edit.nsm.hebrew = green
+edit.nsm.cantillation = blue
+edit.nsm.arabic = brown
+edit.unicode-ls = green
+edit.wide = brightmagenta
+edit.control = yellow
+edit.failed-conversion = brightmagenta
+edit.trim = bold
+edit.wrap = bold
+
+edit.selected = reverse, +bold
+edit.html-tag = bold
+edit.email-quote1 = bold
+# Note: in the following we use 'bold' too, because the
+# console can't show underline.
+edit.emphasized = underline, +bold
+
diff --git a/transtab b/transtab
new file mode 100644
index 0000000..6f9128e
--- /dev/null
+++ b/transtab
@@ -0,0 +1,162 @@
+# This file contains the "translation" table. It is used to convert
+# the character typed immediately following C-q ("translate next charcater").
+
+#####################################
+# Hebrew Points
+#####################################
+
+# בחלק הראשון של קובץ זה, להלן, מפורטות ההתאמות בין המקשים (בהערות אלה
+# אני משתמש במונח "מקש" כשכוונתי לתו (character) העובר המרה -- אין
+# כוונתי למקש הפיזי (key)) לבין סימני הפיסוק העבריים.
+#
+# המקשים הם באנגלית, מכיוון שהתוכנה בודקת את ההתאמות הללו עוד לפני
+# שאמולציית המקלדת העברית נכנסת לפעולה. המשתמש אמור להתייחס לכיתוב העברי
+# שעל גבי המקשים: "s" למעשה פירושה האות "ד" (של "דגש").
+#
+# כללים:
+#
+# 1. האות הראשונה בשם הסימן לרוב היא המקש; למשל, "סגול" - האות היא "ס",
+# ואות זו יושבת על המקש "x". יש כמה יוצאים מן הכלל: "חיריק", שלו מתאים
+# המקש "י" (זאת עשיתי כדי שהמקש "ח" יפיק "חולם"); "קובוץ", שלו מתאים "ו"
+# (זכור! שורוק הוא למעשה ו' עם דגש).
+#
+# 2. אם התנועה חטופה, יש ללחוץ גם על shift, ולכן "חטף סגול" הוא "X".
+#
+# 3. לשווא ולרפה ניתנו סימנים הדומים מבחינה ויזואלית לסימן.
+
+':' 05B0 # HEBREW POINT SHEVA
+'X' 05B1 # HEBREW POINT HATAF SEGOL
+'P' 05B2 # HEBREW POINT HATAF PATAH
+'E' 05B3 # HEBREW POINT HATAF QAMATS
+'h' 05B4 # HEBREW POINT HIRIQ
+'m' 05B5 # HEBREW POINT TSERE
+'x' 05B6 # HEBREW POINT SEGOL
+'p' 05B7 # HEBREW POINT PATAH
+'e' 05B8 # HEBREW POINT QAMATS
+'j' 05B9 # HEBREW POINT HOLAM
+'u' 05BB # HEBREW POINT QUBUTS
+'s' 05BC # HEBREW POINT DAGESH OR MAPIQ (or shuruq)
+'~' 05BF # HEBREW POINT RAFE
+'a' 05C1 # HEBREW POINT SHIN DOT
+'A' 05C2 # HEBREW POINT SIN DOT
+'?' 05BD # HEBREW POINT METEG
+
+# Cantillation marks
+
+# אם אתה משתמש בטעמי המקרא, שייך להם מקשים כרצונך.
+
+'?' 0591 # HEBREW ACCENT ETNAHTA
+'?' 0592 # HEBREW ACCENT SEGOL
+'?' 0593 # HEBREW ACCENT SHALSHELET
+'?' 0594 # HEBREW ACCENT ZAQEF QATAN
+'?' 0595 # HEBREW ACCENT ZAQEF GADOL
+'?' 0596 # HEBREW ACCENT TIPEHA
+'?' 0597 # HEBREW ACCENT REVIA
+'?' 0598 # HEBREW ACCENT ZARQA
+'?' 0599 # HEBREW ACCENT PASHTA
+'?' 059A # HEBREW ACCENT YETIV
+'?' 059B # HEBREW ACCENT TEVIR
+'?' 059C # HEBREW ACCENT GERESH
+'?' 059D # HEBREW ACCENT GERESH MUQDAM
+'?' 059E # HEBREW ACCENT GERSHAYIM
+'?' 059F # HEBREW ACCENT QARNEY PARA
+'?' 05A0 # HEBREW ACCENT TELISHA GEDOLA
+'?' 05A1 # HEBREW ACCENT PAZER
+'?' 05A3 # HEBREW ACCENT MUNAH
+'?' 05A4 # HEBREW ACCENT MAHAPAKH
+'?' 05A5 # HEBREW ACCENT MERKHA
+'?' 05A6 # HEBREW ACCENT MERKHA KEFULA
+'?' 05A7 # HEBREW ACCENT DARGA
+'?' 05A8 # HEBREW ACCENT QADMA
+'?' 05A9 # HEBREW ACCENT TELISHA QETANA
+'?' 05AA # HEBREW ACCENT YERAH BEN YOMO
+'?' 05AB # HEBREW ACCENT OLE
+'?' 05AC # HEBREW ACCENT ILUY
+'?' 05AD # HEBREW ACCENT DEHI
+'?' 05AE # HEBREW ACCENT ZINOR
+'?' 05AF # HEBREW MARK MASORA CIRCLE
+
+#####################################
+# BIDI Formatting Codes
+#####################################
+
+'>' 200E # LEFT-TO-RIGHT MARK
+'<' 200F # RIGHT-TO-LEFT MARK
+'[' 202A # LEFT-TO-RIGHT EMBEDDING
+']' 202B # RIGHT-TO-LEFT EMBEDDING
+'\' 202C # POP DIRECTIONAL FORMATTING
+'{' 202D # LEFT-TO-RIGHT OVERRIDE
+'}' 202E # RIGHT-TO-LEFT OVERRIDE
+
+####################################
+# Punctuation
+####################################
+
+'-' 05BE # HEBREW PUNCTUATION MAQAF
+''' 05F3 # HEBREW PUNCTUATION GERESH
+'"' 05F4 # HEBREW PUNCTUATION GERSHAYIM
+
+'?' 2010 # HYPHEN
+'?' 2011 # NON-BREAKING HYPHEN
+'?' 2013 # EN DASH
+'?' 2014 # EM DASH
+'?' 2018 # LEFT SINGLE QUOTATION MARK
+'?' 2019 # RIGHT SINGLE QUOTATION MARK
+'?' 201A # SINGLE LOW-9 QUOTATION MARK
+'?' 201B # SINGLE HIGH-REVERSED-9 QUOTATION MARK
+'?' 201C # LEFT DOUBLE QUOTATION MARK
+'?' 201D # RIGHT DOUBLE QUOTATION MARK
+'?' 201E # DOUBLE LOW-9 QUOTATION MARK
+'?' 201F # DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+'?' 2212 # MINUS SIGN
+
+####################################
+# Misc
+####################################
+
+'*' 2022 # BULLET
+'_' 0332 # NON-SPACING UNDERSCORE
+'^' 203E # OVERLINE
+ 0D 2028 # LINE SEPARATOR
+'?' 2029 # PARAGRAPH SEPARATOR
+' ' 00A0 # NO-BREAK SPACE
+
+# Numbers are mapped to box-drawing characters. Use the numeric keypad.
+
+'1' '└'
+'2' '┴'
+'3' '┘'
+'4' '│'
+'5' '┼'
+'6' '│'
+'7' '┌'
+'8' '┬'
+'9' '┐'
+
+# Alternative rounded corners
+#'7' '╭'
+#'9' '╮'
+#'1' '╰'
+#'3' '╯'
+
+# Uncomment the following if you want numbers to map to superscripts.
+
+#'0' 2070 # SUPERSCRIPT ZERO
+#'1' 00B9 # SUPERSCRIPT ONE
+#'2' 00B2 # SUPERSCRIPT TWO
+#'3' 00B3 # SUPERSCRIPT THREE
+#'4' 2074 # SUPERSCRIPT FOUR
+#'5' 2075 # SUPERSCRIPT FIVE
+#'6' 2076 # SUPERSCRIPT SIX
+#'7' 2077 # SUPERSCRIPT SEVEN
+#'8' 2078 # SUPERSCRIPT EIGHT
+#'9' 2079 # SUPERSCRIPT NINE
+
+'$' 20AA # NEW SHEQEL SIGN
+ 23 20AC # EURO SIGN
+
+'S' 00A7 # SECTION SIGN
+'?' 00B6 # PILCROW/PARAGRAPH SIGN
+'J' 30C1 # Arbitrary Japanese letter, just for testing double-width terminals.
+
+'?' '?'
diff --git a/transtbl.cc b/transtbl.cc
new file mode 100644
index 0000000..6c4b3d1
--- /dev/null
+++ b/transtbl.cc
@@ -0,0 +1,136 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "transtbl.h"
+#include "io.h" // set_last_error
+#include "dbg.h"
+
+// Most of the code below deals with parsing a TranslationTable
+// file. Such files consist of lines of the form:
+//
+// <character-from> <character-to>
+//
+// that map character-from to character-to.
+//
+// <character-xxx> can be in one of three forms:
+//
+// 1. ' literal-character '
+// 2. decimal-number .
+// 3. hex-number
+//
+// Examples:
+//
+// 'a' 5d0 # maps 'a' to Hebrew letter Alef
+// 'a' 1488. # the same
+// 'a' 'b' # maps 'a' to 'b'
+//
+// literal-character is UTF-8 encoded.
+
+
+// parse_next_char() - parses the next <character> token. (this is a
+// misnomer, because one might think we mean C's "char".)
+//
+// If there was no lexical error, returns a pointer to the end of the
+// token (so one can continue to parse the next token); else returns
+// NULL.
+
+static char *parse_next_char(char *s, unichar &ch)
+{
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (!*s)
+ return NULL;
+ if (*s == '\'') {
+ s++;
+ char *end = strchr(s + 1, '\'');
+ if (!end)
+ return NULL;
+ unistring us;
+ us.init_from_utf8(s, end - s);
+ if (us.size() != 1)
+ return false;
+ ch = us[0];
+ return end + 1;
+ } else {
+ char *end;
+ errno = 0;
+ int val = strtol(s, &end, 16);
+ if (*end == '.') {
+ *end = ' ';
+ val = strtol(s, &end, 10);
+ }
+ if (errno || (*end != '\0' && *end != ' ' && *end != '\t'))
+ return NULL;
+ ch = (unichar)val;
+ return end;
+ }
+}
+
+// load(filename) - loads--that is, parse--a file. It reads the file line by
+// line and for each line calls parse_next_char() to parse the two
+// <character> tokens. It then adds the mapping to the map table.
+
+bool TranslationTable::load(const char *filename)
+{
+#define MAX_LINE_LEN 1024
+ charmap.clear();
+
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ set_last_error(errno);
+ return false;
+ }
+ DBG(1, ("Reading translation table %s\n", filename));
+
+ char line[MAX_LINE_LEN];
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ int len = strlen(line);
+ if (len && line[len-1] == '\n')
+ line[len-1] = 0;
+ if (strchr(line, '#')) // remove comment
+ *(strchr(line, '#')) = '\0';
+
+ unichar ch1, ch2;
+ char *s = line;
+ if ((s = parse_next_char(s, ch1)))
+ if ((s = parse_next_char(s, ch2)))
+ charmap[ch1] = ch2;
+ }
+ fclose(fp);
+
+ return true;
+#undef MAX_LINE_LEN
+}
+
+// translate_char() - matches a character with another, in-place. returns
+// false if no match exists.
+
+bool TranslationTable::translate_char(unichar &ch) const
+{
+ std::map<unichar, unichar>::const_iterator
+ it = charmap.find(ch);
+ if (it != charmap.end()) {
+ ch = it->second;
+ return true;
+ } else
+ return false;
+}
+
diff --git a/transtbl.h b/transtbl.h
new file mode 100644
index 0000000..d205298
--- /dev/null
+++ b/transtbl.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_TRANSTBL_H
+#define BDE_TRANSTBL_H
+
+#include <map>
+
+#include "types.h"
+
+// The TranslationTable class matches one character with another. In other
+// words, It's a hash (map) of characters.
+//
+// For example, the hebrew keyboard emulation is implemented as a
+// TranslationTable that maps english characters to the hebrew characters
+// that sit in their place on the keyboard.
+
+class TranslationTable {
+
+ std::map<unichar, unichar> charmap;
+
+public:
+
+ TranslationTable() { }
+
+ bool empty() const { return charmap.empty(); }
+ bool load(const char *);
+ bool translate_char(unichar &ch) const;
+};
+
+#endif
+
diff --git a/types.cc b/types.cc
new file mode 100644
index 0000000..e642b49
--- /dev/null
+++ b/types.cc
@@ -0,0 +1,209 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#ifdef HAVE_VASPRINTF
+# ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+# endif
+#endif
+#include <string.h> // strlen
+#include <stdio.h> // vsnprintf, vasprintf
+
+#include <algorithm> // find
+
+#include "types.h"
+#include "converters.h" // guess_encoding
+#include "utf8.h"
+
+void unistring::init_from_utf8(const char *s, int len)
+{
+ if (!s) {
+ clear();
+ } else {
+ resize(len);
+ int count = utf8_to_unicode(begin(), s, len);
+ resize(count);
+ }
+}
+
+void unistring::init_from_utf8(const char *s)
+{
+ if (!s)
+ clear();
+ else
+ init_from_utf8(s, strlen(s));
+}
+
+void unistring::init_from_latin1(const char *s)
+{
+ clear();
+ if (s)
+ while (*s)
+ push_back((unsigned char)*s++);
+}
+
+// init_from_filename() - filenames are supposed to be encoded in
+// UTF-8 nowadays, but this is not guaranteed. This method first
+// checks if it looks like UTF-8; if not, it assumes it's a
+// latin1 (ISO-8859-1) encoding.
+
+void unistring::init_from_filename(const char *filename)
+{
+ const char *guess = guess_encoding(filename, strlen(filename));
+ if (guess && STREQ(guess, "UTF-8"))
+ init_from_utf8(filename);
+ else
+ init_from_latin1(filename);
+}
+
+int unistring::index(unichar ch) const
+{
+ int idx = std::find(begin(), end(), ch) - begin();
+ if (idx == len())
+ idx = -1;
+ return idx;
+}
+
+bool unistring::has_char(unichar ch) const
+{
+ return index(ch) != -1;
+}
+
+int unistring::index(const unistring &sub, int from) const
+{
+ if (from >= len())
+ return -1;
+ const unichar *pos = std::search(begin() + from, end(),
+ sub.begin(), sub.end());
+ if (pos != end())
+ return pos - begin();
+ else
+ return -1;
+}
+
+// locale-independent toupper()
+unistring unistring::toupper_ascii() const
+{
+ unistring ret = *this;
+ for (size_type i = 0; i < size(); i++) {
+ if (ret[i] >= 'a' && ret[i] <= 'z')
+ ret[i] += 'A' - 'a';
+ }
+ return ret;
+}
+
+void u8string::init_from_unichars(const unichar *src, int len)
+{
+ char *buf = new char[len * 6 + 1]; // max utf-8 sequence is 6 bytes.
+ buf[ unicode_to_utf8(buf, src, len) ] = 0;
+ *this = buf;
+ delete buf;
+}
+
+void u8string::init_from_unichars(const unistring &str)
+{
+ init_from_unichars(str.begin(), str.size());
+}
+
+int u8string::index(const char *s, int from) const
+{
+ if (from >= len())
+ return -1;
+
+ const char *pos = std::search(&*(begin() + from), &*end(),
+ s, s + strlen(s));
+ if (pos != &*end())
+ return pos - &*begin();
+ else
+ return -1;
+}
+
+inline bool is_ascii_ws(char ch)
+{
+ return ch == ' ' || ch == '\t' || ch == '\n';
+}
+
+void u8string::inplace_trim()
+{
+ while (size() && is_ascii_ws((*this)[0]))
+ erase(begin(), begin()+1);
+ while (size() && is_ascii_ws((*this)[this->size()-1]))
+ erase(end()-1, end());
+}
+
+u8string u8string::trim() const
+{
+ u8string ret = *this;
+ ret.inplace_trim();
+ return ret;
+}
+
+// locale-independent toupper()
+u8string u8string::toupper_ascii() const
+{
+ u8string ret = *this;
+ for (size_type i = 0; i < size(); i++) {
+ if (ret[i] >= 'a' && ret[i] <= 'z')
+ ret[i] += 'A' - 'a';
+ }
+ return ret;
+}
+
+u8string u8string::erase_char(char xch) const
+{
+ u8string ret;
+ for (size_type i = 0; i < size(); i++) {
+ if ((*this)[i] != xch)
+ ret += (*this)[i];
+ }
+ return ret;
+}
+
+void u8string::cformat(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vcformat(fmt, ap);
+ va_end(ap);
+}
+
+void u8string::vcformat(const char *fmt, va_list ap)
+{
+#ifdef HAVE_VASPRINTF
+ char *buf;
+ int result = vasprintf(&buf, fmt, ap);
+ if (result != -1 && buf) {
+ *this = buf;
+ free(buf);
+ } else {
+ clear();
+ }
+#else
+# define MAX_MSG_LEN 4096
+ char buf[MAX_MSG_LEN+1];
+ buf[MAX_MSG_LEN] = 0;
+# ifdef HAVE_VSNPRINTF
+ vsnprintf(buf, MAX_MSG_LEN, fmt, ap);
+# else
+ vsprintf(buf, fmt, ap);
+# endif
+ *this = buf;
+# undef MAX_MSG_LEN
+#endif
+}
+
diff --git a/types.h b/types.h
new file mode 100644
index 0000000..7af75a3
--- /dev/null
+++ b/types.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_TYPES_H
+#define BDE_TYPES_H
+
+#include <string>
+#include <stdarg.h> // u8string::cformat
+
+#include <fribidi/fribidi.h>
+
+#include "directvect.h"
+
+typedef FriBidiChar unichar;
+typedef FriBidiStrIndex idx_t;
+
+class unistring;
+
+class u8string : public std::string {
+
+public:
+
+ u8string() { }
+ u8string(const u8string &s, size_type pos = 0, size_type n = npos)
+ : std::string(s, pos, n) { }
+ u8string(const char *s)
+ : std::string(s) { }
+ u8string(const char *first, const char *last)
+ : std::string(first, last) { }
+ void init_from_unichars(const unichar *src, int len);
+ void init_from_unichars(const unistring &str);
+ u8string(const unistring &str) {
+ init_from_unichars(str);
+ }
+ int len() const { return (int)size(); }
+ void cformat(const char *fmt, ...);
+ void vcformat(const char *fmt, va_list ap);
+ void clear() {
+ erase(begin(), end());
+ }
+ int index(char c, int from = 0) const {
+ size_type pos = find_first_of(c, from);
+ return pos == npos ? -1 : (int)pos;
+ }
+ int rindex(char c) const {
+ size_type pos = find_last_of(c);
+ return pos == npos ? -1 : (int)pos;
+ }
+ int index(const char *s, int from = 0) const;
+ u8string substr(int from, int len = -1) const {
+ if (len == -1 || (from + len > (int)size()))
+ len = size() - from;
+ return u8string(&*(begin() + from), &*(begin() + from + len));
+ }
+ u8string erase_char(char xch) const;
+ u8string trim() const;
+ void inplace_trim();
+ u8string toupper_ascii() const;
+};
+
+// we use "cstring" in the source code when we want to
+// emphasize that this is not a UTF-8 encoded string.
+#define cstring u8string
+
+class unistring : public DirectVector<unichar> {
+
+public:
+
+ unistring() { }
+ unistring(size_type n)
+ : DirectVector<unichar>(n) { }
+ unistring (const unichar *first, const unichar *last) {
+ insert(end(), first, last);
+ }
+
+ void init_from_utf8(const char *s, int len);
+ void init_from_utf8(const char *first, const char *last) {
+ init_from_utf8(first, last - first);
+ }
+ void init_from_utf8(const char *s);
+ unistring(const u8string &u8) {
+ init_from_utf8(u8.c_str());
+ }
+ void init_from_latin1(const char *s);
+ void init_from_filename(const char *filename);
+ idx_t len() const { return (idx_t)size(); }
+
+ void append(const unichar *s, size_type len) {
+ insert(end(), s, s + len);
+ }
+ void append(const unistring &s, size_type len) {
+ insert(end(), s.begin(), s.begin() + len);
+ }
+ void append(const unistring &s) {
+ insert(end(), s.begin(), s.end());
+ }
+ void erase_head(size_type len) {
+ erase(begin(), begin() + len);
+ }
+ unistring substr(int from, int len = -1) const {
+ if (len == -1 || (from + len > (int)size()))
+ len = size() - from;
+ return unistring(begin() + from, begin() + from + len);
+ }
+ unistring toupper_ascii() const;
+ int index(unichar ch) const;
+ bool has_char(unichar ch) const;
+ int index(const unistring &sub, int from = 0) const;
+};
+
+#define STREQ(a,b) (strcmp(a, b) == 0)
+
+#endif
+
diff --git a/undo.cc b/undo.cc
new file mode 100644
index 0000000..d8b9dea
--- /dev/null
+++ b/undo.cc
@@ -0,0 +1,183 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include "undo.h"
+#include "dbg.h"
+
+#define DEFAULT_LIMIT 4000
+#define RESERVE 1000
+
+UndoStack::UndoStack()
+{
+ top = 0;
+ merge_small_ops = false;
+ bytes_size = 0;
+ bytes_size_limit = DEFAULT_LIMIT;
+ truncated = false;
+}
+
+void UndoStack::clear()
+{
+ stack.clear();
+ bytes_size = 0;
+ top = 0;
+ truncated = false;
+}
+
+// get_prev_op() - returns a pointer to the last operation made to the buffer.
+// returns NULL if the undo stack is empty.
+
+UndoOp* UndoStack::get_prev_op()
+{
+ if (top == 0)
+ return NULL;
+ return &stack[--top];
+}
+
+// get_next_op() - returns a pointer to the last operation that was undone.
+
+UndoOp *UndoStack::get_next_op()
+{
+ if (top == stack.size())
+ return NULL;
+ return &stack[top++];
+}
+
+void UndoStack::set_size_limit(size_t limit)
+{
+ bytes_size_limit = limit;
+ clear();
+}
+
+void UndoStack::update_size_up(int chars_count)
+{
+ bytes_size += chars_count * sizeof(unichar);
+}
+
+void UndoStack::update_size_up(const UndoOp &op)
+{
+ bytes_size += op.calc_size();
+}
+
+void UndoStack::update_size_down(const UndoOp &op)
+{
+ bytes_size -= op.calc_size();
+}
+
+// truncate_undo() - called when the stack size is too big. it erases the
+// old operations.
+
+void UndoStack::truncate_undo()
+{
+ unsigned new_size = bytes_size_limit;
+ // we have to reserve some space in order not to reach the
+ // size-limit very soon.
+ if (new_size > RESERVE)
+ new_size -= RESERVE;
+ else
+ new_size = 0;
+
+ unsigned i = 0;
+ while (i < stack.size() && bytes_size > new_size) {
+ update_size_down(stack[i++]);
+ }
+ stack.erase(stack.begin(), stack.begin() + i);
+ top -= i;
+
+ truncated = true;
+}
+
+// merge() - merges a new operation with the last operation. returns false if
+// that was not possible.
+//
+// An example: suppose the last operation was to insert "a" into the buffer.
+// If the user now types "b", merge() will change the last operation to record
+// an insertion of "ab".
+//
+// However, if the user moves the cursor to a different location before typing
+// "b", such a merge is not possible, because "a" and "b" are not adjacent.
+
+bool UndoOp::merge(const UndoOp &op)
+{
+ if (type != op.type)
+ return false;
+ if (op.inserted_text.len() + op.deleted_text.len() != 1)
+ return false;
+ if (point.para != op.point.para)
+ return false;
+
+ switch (type) {
+ case opInsert:
+ if (op.point.pos == point.pos + inserted_text.len()) {
+ inserted_text.append(op.inserted_text);
+ return true;
+ }
+ break;
+ case opDelete:
+ if (op.point.pos == point.pos) {
+ deleted_text.append(op.deleted_text);
+ return true;
+ }
+ if (op.point.pos == point.pos - 1) {
+ point.pos--;
+ deleted_text.insert(deleted_text.begin(),
+ op.deleted_text.begin(),
+ op.deleted_text.end());
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+// erase_redo_ops() - erases the operations that were undone.
+
+void UndoStack::erase_redo_ops()
+{
+ for (unsigned i = top; i < stack.size(); i++)
+ update_size_down(stack[i]);
+ stack.erase(stack.begin() + top, stack.end());
+}
+
+// record_op() - records an operation on the stack.
+
+void UndoStack::record_op(const UndoOp &op)
+{
+ if (disabled())
+ return;
+
+ // recording an opearation erases the recordings of all the operations
+ // that were undone till now.
+ if (is_redo_available())
+ erase_redo_ops();
+
+ if (undo_size_too_big())
+ truncate_undo();
+
+ // first try to merge this operation with the previous one. if that
+ // fails, record it as a separate operation.
+ if (merge_small_ops && !stack.empty() && stack.back().merge(op)) {
+ update_size_up(1); // one character was recorded
+ } else {
+ stack.push_back(op);
+ update_size_up(op);
+ top++;
+ }
+}
+
diff --git a/undo.h b/undo.h
new file mode 100644
index 0000000..3307205
--- /dev/null
+++ b/undo.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_UNDO_H
+#define BDE_UNDO_H
+
+#include <vector>
+
+#include "types.h"
+#include "point.h"
+
+// Class UndoStack records the operations (ops) made to EditBox's buffer.
+//
+// Strictly speaking, the UndoStack is not really a stack: you move the
+// "top" pointer up and down within this stack when you undo and redo
+// operations.
+//
+// UndoStack stores the operations in a vector of UndoOp's. UndoOp contains
+// all the information needed to restore one operation.
+
+enum OpType { opInsert, opDelete, opReplace };
+
+struct UndoOp {
+
+ OpType type;
+ Point point; // the point in the buffer where the operation was made
+ unistring inserted_text;
+ unistring deleted_text;
+
+ size_t calc_size() const {
+ return
+ sizeof(UndoOp)
+ + inserted_text.len() * sizeof(unichar)
+ + deleted_text.len() * sizeof(unichar);
+ }
+ bool merge(const UndoOp &op);
+};
+
+class UndoStack {
+
+private:
+
+ std::vector<UndoOp> stack;
+ unsigned top;
+
+ size_t bytes_size; // size of current stack
+
+ size_t bytes_size_limit; // max size
+
+ bool merge_small_ops; // merge small ops?
+
+ bool truncated; // was the stack already truncated
+ // to fit bytes_size_limit?
+
+private:
+
+ void erase_redo_ops();
+
+ void truncate_undo();
+ bool undo_size_too_big() const { return bytes_size >= bytes_size_limit; }
+ void update_size_up(int chars_count);
+ void update_size_up(const UndoOp &op);
+ void update_size_down(const UndoOp &op);
+
+public:
+
+ UndoStack();
+ void clear();
+
+ bool was_truncated() { return truncated; }
+ bool disabled() const { return bytes_size_limit == 0; }
+
+ bool is_undo_available() const { return (top != 0); }
+ bool is_redo_available() const { return (top != stack.size()); }
+
+ void record_op(const UndoOp &op);
+ UndoOp *get_prev_op();
+ UndoOp *get_next_op();
+
+ void set_size_limit(size_t limit);
+ void set_merge(bool value) { merge_small_ops = value; }
+ bool is_merge() const { return merge_small_ops; }
+};
+
+#endif
+
diff --git a/univalues.h b/univalues.h
new file mode 100644
index 0000000..098e48a
--- /dev/null
+++ b/univalues.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_UNIVALUES_H
+#define BDE_UNIVALUES_H
+
+// Line Separator and Paragraph Separator
+
+#define UNICODE_LS 0x2028
+#define UNICODE_PS 0x2029
+
+// BIDI formatting codes
+
+#define UNI_LRM 0x200E
+#define UNI_RLM 0x200F
+#define UNI_LRE 0x202A
+#define UNI_RLE 0x202B
+#define UNI_PDF 0x202C
+#define UNI_LRO 0x202D
+#define UNI_RLO 0x202E
+
+// Hebrew codes, mainly points and punctuations
+
+#define UNI_HEB_ALEF 0x05D0
+#define UNI_HEB_TAV 0x05EA
+#define UNI_HEB_GERESH 0x05F3
+#define UNI_HEB_GERSHAYIM 0x05F4
+#define UNI_HEB_SHEVA 0x05B0
+#define UNI_HEB_HATAF_SEGOL 0x05B1
+#define UNI_HEB_HATAF_PATAH 0x05B2
+#define UNI_HEB_HATAF_QAMATS 0x05B3
+#define UNI_HEB_HIRIQ 0x05B4
+#define UNI_HEB_TSERE 0x05B5
+#define UNI_HEB_SEGOL 0x05B6
+#define UNI_HEB_PATAH 0x05B7
+#define UNI_HEB_QAMATS 0x05B8
+#define UNI_HEB_HOLAM 0x05B9
+#define UNI_HEB_QUBUTS 0x05BB
+#define UNI_HEB_DAGESH_OR_MAPIQ 0x05BC
+#define UNI_HEB_METEG 0x05BD
+#define UNI_HEB_MAQAF 0x05BE
+#define UNI_HEB_RAFE 0x05BF
+#define UNI_HEB_PASEQ 0x05C0
+#define UNI_HEB_SHIN_DOT 0x05C1
+#define UNI_HEB_SIN_DOT 0x05C2
+#define UNI_HEB_SOF_PASUQ 0x05C3
+#define UNI_HEB_UPPER_DOT 0x05C4
+
+// Hebrew cantillation marks
+
+#define UNI_HEB_ETNAHTA 0x0591
+#define UNI_HEB_MASORA_CIRCLE 0x05AF
+// in the above range, 0x05A2 is not allocated
+
+// Arabic harakats
+#define UNI_ARA_FATHATAN 0x064B
+#define UNI_ARA_SUKUN 0x0652
+#define UNI_ARA_SUPERSCIPT_ALEF 0x0670
+
+// Other punctuation
+
+#define UNI_HYPHEN 0x2010
+#define UNI_NON_BREAKING_HYPHEN 0x2011
+#define UNI_EN_DASH 0x2013
+#define UNI_EM_DASH 0x2014
+#define UNI_LEFT_SINGLE_QUOTE 0x2018
+#define UNI_RIGHT_SINGLE_QUOTE 0x2019
+#define UNI_SINGLE_LOW9_QUOTE 0x201A
+#define UNI_SINGLE_HIGH_REV9_QUOTE 0x201B
+#define UNI_LEFT_DOUBLE_QUOTE 0x201C
+#define UNI_RIGHT_DOUBLE_QUOTE 0x201D
+#define UNI_DOUBLE_LOW9_QUOTE 0x201E
+#define UNI_DOUBLE_HIGH_REV9_QUOTE 0x201F
+#define UNI_MINUS_SIGN 0x2212
+#define UNI_BULLET 0x2022
+
+// Misc
+
+#define UNI_REPLACEMENT 0xFFFD
+#define UNI_NS_UNDERSCORE 0x0332
+#define UNI_NO_BREAK_SPACE 0x00A0
+
+// Arabic
+
+#define UNI_ZWNJ 0x200C
+#define UNI_ZWJ 0x200D
+
+#endif
+
diff --git a/utf8.cc b/utf8.cc
new file mode 100644
index 0000000..6b29563
--- /dev/null
+++ b/utf8.cc
@@ -0,0 +1,149 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "utf8.h"
+#include "univalues.h"
+#include "dbg.h"
+
+// utf8_to_unicode() - converts a UTF-8 string to unichars. When an
+// incomplete sequence is encountered, *problem will point to its head (it's
+// similar to what iconv does).
+//
+// This function converts UTF-8 to UCS-4 (not to UTF-32) -- that's why it
+// recognizes 5- and 6-byte sequences.
+
+int utf8_to_unicode(unichar *dest, const char *s, int len, const char **problem)
+{
+ int length = 0;
+ const char *end = s + len;
+
+ if (problem)
+ *problem = NULL;
+
+// constant expressions are evaluated at compile time, of course.
+#define FRST(t) ((*s & ((1 << (8-t-1)) - 1)) << (t-1)*6)
+#define UC(t,n) (*(s+n-1) & 0x3F) << ((t-n)*6)
+ while (s < end) {
+ if (!(*s & 0x80)) {
+ *dest++ = *s;
+ s++;
+ } else if ((*s & 0xE0) == 0xC0) {
+ if ((end - s) >= 2) {
+ *dest++ = FRST(2) | UC(2,2);
+ s += 2;
+ } else {
+ if (problem)
+ *problem = s;
+ break;
+ }
+ } else if ((*s & 0xF0) == 0xE0) {
+ if ((end - s) >= 3) {
+ *dest++ = FRST(3) | UC(3,2) | UC(3,3);
+ s += 3;
+ } else {
+ if (problem)
+ *problem = s;
+ break;
+ }
+ } else if ((*s & 0xF8) == 0xF0) {
+ if ((end - s) >= 4) {
+ *dest++ = FRST(4) | UC(4,2) | UC(4,3) | UC(4,4);
+ s += 4;
+ } else {
+ if (problem)
+ *problem = s;
+ break;
+ }
+ } else if ((*s & 0xFC) == 0xF8) {
+ if ((end - s) >= 5) {
+ *dest++ = FRST(5) | UC(5,2) | UC(5,3) | UC(5,4) | UC(5,5);
+ s += 5;
+ } else {
+ if (problem)
+ *problem = s;
+ break;
+ }
+ } else if ((*s & 0xFE) == 0xFC) {
+ if ((end - s) >= 6) {
+ *dest++ = FRST(6) | UC(6,2) | UC(6,3) | UC(6,4) | UC(6,5) | UC(6,6);
+ s += 6;
+ } else {
+ if (problem)
+ *problem = s;
+ break;
+ }
+ } else {
+ *dest++ = UNI_REPLACEMENT;
+ s++;
+ }
+ length++;
+ }
+ return length;
+#undef FRST
+#undef UC
+}
+
+// unicode_to_utf8() - converts unichars to UTF-8.
+
+int unicode_to_utf8(char *dest, const unichar *us, int len)
+{
+#define UC(n) ((*us >> 6*n) & 0x3F)
+#define CNT(n) (((1 << n) - 1) << (8 - n))
+ int nbytes = 0;
+ while (len--) {
+ if (*us < 0x80) {
+ *dest++ = *us;
+ nbytes += 1;
+ } else if (*us < 0x800) {
+ *dest++ = UC(1) | CNT(2);
+ *dest++ = UC(0) | 0x80;
+ nbytes += 2;
+ } else if (*us < 0x10000) {
+ *dest++ = UC(2) | CNT(3);
+ *dest++ = UC(1) | 0x80;
+ *dest++ = UC(0) | 0x80;
+ nbytes += 3;
+ } else if (*us < 0x200000) {
+ *dest++ = UC(3) | CNT(4);
+ *dest++ = UC(2) | 0x80;
+ *dest++ = UC(1) | 0x80;
+ *dest++ = UC(0) | 0x80;
+ nbytes += 4;
+ } else if (*us < 0x4000000) {
+ *dest++ = UC(4) | CNT(5);
+ *dest++ = UC(3) | 0x80;
+ *dest++ = UC(2) | 0x80;
+ *dest++ = UC(1) | 0x80;
+ *dest++ = UC(0) | 0x80;
+ nbytes += 5;
+ } else {
+ *dest++ = UC(5) | CNT(6);
+ *dest++ = UC(4) | 0x80;
+ *dest++ = UC(3) | 0x80;
+ *dest++ = UC(2) | 0x80;
+ *dest++ = UC(1) | 0x80;
+ *dest++ = UC(0) | 0x80;
+ nbytes += 6;
+ }
+ us++;
+ }
+ return nbytes;
+#undef UC
+#undef CNT
+}
+
diff --git a/utf8.h b/utf8.h
new file mode 100644
index 0000000..17e234c
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_UTF8_H
+#define BDE_UTF8_H
+
+#include "types.h"
+
+int utf8_to_unicode(unichar *dest, const char *s, int len, const char **problem = NULL);
+int unicode_to_utf8(char *dest, const unichar *us, int len);
+
+#endif
+
diff --git a/widget.cc b/widget.cc
new file mode 100644
index 0000000..97840fe
--- /dev/null
+++ b/widget.cc
@@ -0,0 +1,118 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#include <config.h>
+
+#include "widget.h"
+#include "mk_wcwidth.h"
+#include "bidi.h"
+#include "shaping.h"
+#include "my_wctob.h"
+
+Widget::Widget()
+{
+ wnd = NULL;
+ modal = false;
+}
+
+Widget::~Widget()
+{
+ destroy_window();
+}
+
+bool Widget::create_window(int lines, int cols)
+{
+ if (terminal::is_interactive()) {
+ wnd = newwin(lines, cols, 0, 0);
+ enable_keypad();
+ }
+ return wnd != NULL;
+}
+
+void Widget::resize(int lines, int columns, int y, int x)
+{
+ wresize(wnd, lines, columns);
+ mvwin(wnd, y, x);
+}
+
+void Widget::put_unichar(unichar ch, unichar bad_repr)
+{
+#ifdef HAVE_WIDE_CURSES
+ if (!terminal::is_utf8 && WCTOB(ch) == EOF)
+ ch = bad_repr;
+ waddnwstr(wnd, (wchar_t*)&ch, 1);
+#else
+ int ich = terminal::force_iso88598 ? unicode_to_iso88598(ch) : WCTOB(ch);
+ if (ich == EOF)
+ ich = bad_repr;
+ waddch(wnd, (unsigned char)ich);
+#endif
+}
+
+// draw_string() - draws a UTF-8 string. This is a very simple routine and
+// it's used by the most simple widgets only (like Label).
+
+void Widget::draw_string(const char *u8, bool align_right)
+{
+ unistring text, vis_text;
+ text.init_from_utf8(u8);
+
+ // trim string to fit window width
+ int wnd_x, dummy;
+ getyx(wnd, dummy, wnd_x);
+ int swidth = wnd_x;
+ unichar *trim_pos = text.begin();
+ while (trim_pos < text.end()) {
+ int char_width = mk_wcwidth(*trim_pos);
+ if (swidth + char_width > window_width())
+ break;
+ swidth += char_width;
+ trim_pos++;
+ }
+ text.erase(trim_pos, text.end());
+
+ // convert to visual
+ direction_t dir = BiDi::determine_base_dir(text.begin(), text.size(),
+ algoUnicode);
+ BiDi::simple_log2vis(text, dir, vis_text);
+
+ if (terminal::do_arabic_shaping) {
+ int new_len = shape(vis_text.begin(), vis_text.len());
+ swidth -= vis_text.len() - new_len;
+ vis_text.resize(new_len);
+ }
+
+ // draw the string
+ if (align_right && dir == dirRTL)
+ wmove(wnd, 0, window_width() - swidth);
+ for (int i = 0; i < vis_text.len(); i++) {
+ put_unichar(vis_text[i], '?');
+ }
+
+ // reposition the cursor
+ if (align_right && dir == dirRTL)
+ wmove(wnd, 0, window_width() - swidth - 1);
+}
+
+void Widget::signal_error()
+{
+ // I don't like those awful beeps,
+ // but people may not be familiar with flashes.
+
+ //flash();
+ beep();
+}
+
diff --git a/widget.h b/widget.h
new file mode 100644
index 0000000..7ed78d9
--- /dev/null
+++ b/widget.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+#ifndef BDE_WIDGET_H
+#define BDE_WIDGET_H
+
+#include "dispatcher.h"
+#include "terminal.h"
+
+typedef int attribute_t;
+
+class Widget : public Dispatcher {
+
+ bool modal;
+
+public:
+
+ WINDOW *wnd; // public
+
+ Widget();
+ virtual ~Widget();
+
+ bool create_window(int lines = 1, int cols = 5);
+
+ void destroy_window() {
+ if (wnd) {
+ delwin(wnd);
+ wnd = NULL;
+ }
+ }
+
+ bool is_valid_window() const {
+ return wnd != NULL;
+ }
+
+ void enable_keypad() {
+ if (is_valid_window())
+ keypad(wnd, TRUE);
+ }
+
+ int window_width() const {
+ int x, y;
+ getmaxyx(wnd, y, x);
+ return x;
+ }
+
+ int window_height() const {
+ int x, y;
+ getmaxyx(wnd, y, x);
+ return y;
+ }
+
+ int window_begx() const {
+ int x, y;
+ getbegyx(wnd, y, x);
+ return x;
+ }
+
+ int window_begy() const {
+ int x, y;
+ getbegyx(wnd, y, x);
+ return y;
+ }
+
+ void put_unichar(unichar ch, unichar bad_repr);
+ void draw_string(const char *u8, bool align_right = false);
+
+ virtual void resize(int lines, int columns, int y, int x);
+ virtual void update() = 0;
+ virtual bool is_dirty() const = 0;
+ virtual void invalidate_view() = 0;
+
+ virtual void end_modal() { modal = false; }
+ virtual void set_modal() { modal = true; }
+ virtual bool is_modal() const { return modal; }
+
+ static void signal_error();
+};
+
+#endif
+