summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am30
-rw-r--r--src/Makefile.in536
-rw-r--r--src/browser.c1078
-rw-r--r--src/chars.c963
-rw-r--r--src/color.c431
-rw-r--r--src/cut.c291
-rw-r--r--src/files.c3041
-rw-r--r--src/global.c1730
-rw-r--r--src/help.c563
-rw-r--r--src/move.c670
-rw-r--r--src/nano.c2735
-rw-r--r--src/nano.h794
-rw-r--r--src/prompt.c1363
-rw-r--r--src/proto.h863
-rw-r--r--src/rcfile.c1305
-rw-r--r--src/search.c1498
-rw-r--r--src/text.c3079
-rw-r--r--src/utils.c674
-rw-r--r--src/winio.c3586
19 files changed, 25230 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..f0b21c0
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,30 @@
+localedir = $(datadir)/locale
+INCLUDES = -DLOCALEDIR=\"$(localedir)\" -DSYSCONFDIR=\"$(sysconfdir)\"
+
+ACLOCAL_AMFLAGS = -I m4
+
+bin_PROGRAMS = nano
+nano_SOURCES = browser.c \
+ chars.c \
+ color.c \
+ cut.c \
+ files.c \
+ global.c \
+ help.c \
+ move.c \
+ nano.c \
+ nano.h \
+ prompt.c \
+ proto.h \
+ rcfile.c \
+ search.c \
+ text.c \
+ utils.c \
+ winio.c
+
+nano_LDADD = @GLIB_LIBS@ @LIBINTL@
+
+install-exec-hook:
+ cd $(DESTDIR)$(bindir) && rm -f rnano && $(LN_S) nano rnano
+uninstall-hook:
+ cd $(DESTDIR)$(bindir) && rm -f rnano
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..acb3389
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,536 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+bin_PROGRAMS = nano$(EXEEXT)
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_define_dir.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/isc-posix.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_nano_OBJECTS = browser.$(OBJEXT) chars.$(OBJEXT) color.$(OBJEXT) \
+ cut.$(OBJEXT) files.$(OBJEXT) global.$(OBJEXT) help.$(OBJEXT) \
+ move.$(OBJEXT) nano.$(OBJEXT) prompt.$(OBJEXT) \
+ rcfile.$(OBJEXT) search.$(OBJEXT) text.$(OBJEXT) \
+ utils.$(OBJEXT) winio.$(OBJEXT)
+nano_OBJECTS = $(am_nano_OBJECTS)
+nano_DEPENDENCIES =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(nano_SOURCES)
+DIST_SOURCES = $(nano_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CURSES_LIB = @CURSES_LIB@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GMSGFMT = @GMSGFMT@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKGDATADIR = @PKGDATADIR@
+PKG_CONFIG = @PKG_CONFIG@
+POSUB = @POSUB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = $(datadir)/locale
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = -DLOCALEDIR=\"$(localedir)\" -DSYSCONFDIR=\"$(sysconfdir)\"
+ACLOCAL_AMFLAGS = -I m4
+nano_SOURCES = browser.c \
+ chars.c \
+ color.c \
+ cut.c \
+ files.c \
+ global.c \
+ help.c \
+ move.c \
+ nano.c \
+ nano.h \
+ prompt.c \
+ proto.h \
+ rcfile.c \
+ search.c \
+ text.c \
+ utils.c \
+ winio.c
+
+nano_LDADD = @GLIB_LIBS@ @LIBINTL@
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+nano$(EXEEXT): $(nano_OBJECTS) $(nano_DEPENDENCIES)
+ @rm -f nano$(EXEEXT)
+ $(LINK) $(nano_OBJECTS) $(nano_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/browser.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chars.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cut.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/files.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/global.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/move.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nano.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prompt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rcfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/text.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/winio.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-hook
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) uninstall-hook
+.MAKE: install-am install-exec-am install-strip uninstall-am
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic ctags distclean distclean-compile \
+ distclean-generic distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-exec-hook install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-hook
+
+
+install-exec-hook:
+ cd $(DESTDIR)$(bindir) && rm -f rnano && $(LN_S) nano rnano
+uninstall-hook:
+ cd $(DESTDIR)$(bindir) && rm -f rnano
+
+# 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/src/browser.c b/src/browser.c
new file mode 100644
index 0000000..3d32149
--- /dev/null
+++ b/src/browser.c
@@ -0,0 +1,1078 @@
+/* $Id: browser.c 4527 2011-02-07 14:45:56Z astyanax $ */
+/**************************************************************************
+ * browser.c *
+ * *
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 *
+ * Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifndef DISABLE_BROWSER
+
+static char **filelist = NULL;
+ /* The list of files to display in the file browser. */
+static size_t filelist_len = 0;
+ /* The number of files in the list. */
+static int width = 0;
+ /* The number of files that we can display per line. */
+static int longest = 0;
+ /* The number of columns in the longest filename in the list. */
+static size_t selected = 0;
+ /* The currently selected filename in the list. This variable
+ * is zero-based. */
+static bool search_last_file = FALSE;
+ /* Have we gone past the last file while searching? */
+
+/* Our main file browser function. path is the tilde-expanded path we
+ * start browsing from. */
+char *do_browser(char *path, DIR *dir)
+{
+ char *retval = NULL;
+ int kbinput;
+ bool meta_key, func_key, old_const_update = ISSET(CONST_UPDATE);
+ bool abort = FALSE;
+ /* Whether we should abort the file browser. */
+ char *prev_dir = NULL;
+ /* The directory we were in, if any, before backing up via
+ * browsing to "..". */
+ char *ans = NULL;
+ /* The last answer the user typed at the statusbar prompt. */
+ size_t old_selected;
+ /* The selected file we had before the current selected file. */
+ const sc *s;
+ const subnfunc *f;
+
+ curs_set(0);
+ blank_statusbar();
+#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
+ currmenu = MBROWSER;
+#endif
+ bottombars(MBROWSER);
+ wnoutrefresh(bottomwin);
+
+ UNSET(CONST_UPDATE);
+
+ ans = mallocstrcpy(NULL, "");
+
+ change_browser_directory:
+ /* We go here after we select a new directory. */
+
+ /* Start with no key pressed. */
+ kbinput = ERR;
+
+ path = mallocstrassn(path, get_full_path(path));
+
+ assert(path != NULL && path[strlen(path) - 1] == '/');
+
+ /* Get the file list, and set longest and width in the process. */
+ browser_init(path, dir);
+
+ assert(filelist != NULL);
+
+ /* Sort the file list. */
+ qsort(filelist, filelist_len, sizeof(char *), diralphasort);
+
+ /* If prev_dir isn't NULL, select the directory saved in it, and
+ * then blow it away. */
+ if (prev_dir != NULL) {
+ browser_select_filename(prev_dir);
+
+ free(prev_dir);
+ prev_dir = NULL;
+ /* Otherwise, select the first file or directory in the list. */
+ } else
+ selected = 0;
+
+ old_selected = (size_t)-1;
+
+ titlebar(path);
+
+ while (!abort) {
+ struct stat st;
+ int i;
+ size_t fileline = selected / width;
+ /* The line number the selected file is on. */
+ char *new_path;
+ /* The path we switch to at the "Go to Directory"
+ * prompt. */
+
+ /* Display the file list if we don't have a key, or if the
+ * selected file has changed, and set width in the process. */
+ if (kbinput == ERR || old_selected != selected)
+ browser_refresh();
+
+ old_selected = selected;
+
+ kbinput = get_kbinput(edit, &meta_key, &func_key);
+
+#ifndef DISABLE_MOUSE
+ if (kbinput == KEY_MOUSE) {
+
+ int mouse_x, mouse_y;
+
+ /* We can click on the edit window to select a
+ * filename. */
+ if (get_mouseinput(&mouse_x, &mouse_y, TRUE) == 0 &&
+ wmouse_trafo(edit, &mouse_y, &mouse_x, FALSE)) {
+ /* longest is the width of each column. There
+ * are two spaces between each column. */
+ selected = (fileline / editwinrows) *
+ (editwinrows * width) + (mouse_y *
+ width) + (mouse_x / (longest + 2));
+
+ /* If they clicked beyond the end of a row,
+ * select the filename at the end of that
+ * row. */
+ if (mouse_x > width * (longest + 2))
+ selected--;
+
+ /* If we're off the screen, select the last
+ * filename. */
+ if (selected > filelist_len - 1)
+ selected = filelist_len - 1;
+
+ /* If we selected the same filename as last
+ * time, put back the Enter key so that it's
+ * read in. */
+ if (old_selected == selected)
+ unget_kbinput(sc_seq_or(do_enter_void, 0), FALSE, FALSE);
+ }
+ }
+#endif /* !DISABLE_MOUSE */
+
+ parse_browser_input(&kbinput, &meta_key, &func_key);
+ s = get_shortcut(MBROWSER, &kbinput, &meta_key, &func_key);
+ if (!s)
+ continue;
+ f = sctofunc((sc *) s);
+ if (!f)
+ break;
+
+ if (f->scfunc == total_refresh) {
+ total_redraw();
+ } else if (f->scfunc == do_help_void) {
+#ifndef DISABLE_HELP
+ do_browser_help();
+ curs_set(0);
+#else
+ nano_disabled_msg();
+#endif
+ /* Search for a filename. */
+ } else if (f->scfunc == do_search) {
+ curs_set(1);
+ do_filesearch();
+ curs_set(0);
+ /* Search for another filename. */
+ } else if (f->scfunc == do_research) {
+ do_fileresearch();
+ } else if (f->scfunc == do_page_up) {
+ if (selected >= (editwinrows + fileline % editwinrows) *
+ width)
+ selected -= (editwinrows + fileline % editwinrows) *
+ width;
+ else
+ selected = 0;
+ } else if (f->scfunc == do_page_down) {
+ selected += (editwinrows - fileline % editwinrows) *
+ width;
+ if (selected > filelist_len - 1)
+ selected = filelist_len - 1;
+ } else if (f->scfunc == do_first_file) {
+ if (meta_key)
+ selected = 0;
+ } else if (f->scfunc == do_last_file) {
+ if (meta_key)
+ selected = filelist_len - 1;
+ /* Go to a specific directory. */
+ } else if (f->scfunc == goto_dir_void) {
+ curs_set(1);
+
+ i = do_prompt(TRUE,
+#ifndef DISABLE_TABCOMP
+ FALSE,
+#endif
+ MGOTODIR, ans,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ NULL,
+#endif
+ browser_refresh, N_("Go To Directory"));
+
+ curs_set(0);
+#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
+ currmenu = MBROWSER;
+#endif
+ bottombars(MBROWSER);
+
+ /* If the directory begins with a newline (i.e. an
+ * encoded null), treat it as though it's blank. */
+ if (i < 0 || *answer == '\n') {
+ /* We canceled. Indicate that on the statusbar, and
+ * blank out ans, since we're done with it. */
+ statusbar(_("Cancelled"));
+ ans = mallocstrcpy(ans, "");
+ continue;
+ } else if (i != 0) {
+ /* Put back the "Go to Directory" key and save
+ * answer in ans, so that the file list is displayed
+ * again, the prompt is displayed again, and what we
+ * typed before at the prompt is displayed again. */
+ unget_kbinput(sc_seq_or(do_gotolinecolumn_void, 0), FALSE, FALSE);
+ ans = mallocstrcpy(ans, answer);
+ continue;
+ }
+
+ /* We have a directory. Blank out ans, since we're done
+ * with it. */
+ ans = mallocstrcpy(ans, "");
+
+ /* Convert newlines to nulls, just before we go to the
+ * directory. */
+ sunder(answer);
+ align(&answer);
+
+ new_path = real_dir_from_tilde(answer);
+
+ if (new_path[0] != '/') {
+ new_path = charealloc(new_path, strlen(path) +
+ strlen(answer) + 1);
+ sprintf(new_path, "%s%s", path, answer);
+ }
+
+#ifndef DISABLE_OPERATINGDIR
+ if (check_operating_dir(new_path, FALSE)) {
+ statusbar(
+ _("Can't go outside of %s in restricted mode"),
+ operating_dir);
+ free(new_path);
+ continue;
+ }
+#endif
+
+ dir = opendir(new_path);
+ if (dir == NULL) {
+ /* We can't open this directory for some reason.
+ * Complain. */
+ statusbar(_("Error reading %s: %s"), answer,
+ strerror(errno));
+ beep();
+ free(new_path);
+ continue;
+ }
+
+ /* Start over again with the new path value. */
+ free(path);
+ path = new_path;
+ goto change_browser_directory;
+ } else if (f->scfunc == do_up_void) {
+ if (selected >= width)
+ selected -= width;
+ } else if (f->scfunc == do_left) {
+ if (selected > 0)
+ selected--;
+ } else if (f->scfunc == do_down_void) {
+ if (selected + width <= filelist_len - 1)
+ selected += width;
+ } else if (f->scfunc == do_right) {
+ if (selected < filelist_len - 1)
+ selected++;
+ } else if (f->scfunc == do_enter_void) {
+ /* We can't move up from "/". */
+ if (strcmp(filelist[selected], "/..") == 0) {
+ statusbar(_("Can't move up a directory"));
+ beep();
+ continue;
+ }
+
+#ifndef DISABLE_OPERATINGDIR
+ /* Note: The selected file can be outside the operating
+ * directory if it's ".." or if it's a symlink to a
+ * directory outside the operating directory. */
+ if (check_operating_dir(filelist[selected], FALSE)) {
+ statusbar(
+ _("Can't go outside of %s in restricted mode"),
+ operating_dir);
+ beep();
+ continue;
+ }
+#endif
+
+ if (stat(filelist[selected], &st) == -1) {
+ /* We can't open this file for some reason.
+ * Complain. */
+ statusbar(_("Error reading %s: %s"),
+ filelist[selected], strerror(errno));
+ beep();
+ continue;
+ }
+
+ /* If we've successfully opened a file, we're done, so
+ * get out. */
+ if (!S_ISDIR(st.st_mode)) {
+ retval = mallocstrcpy(NULL, filelist[selected]);
+ abort = TRUE;
+ continue;
+ /* If we've successfully opened a directory, and it's
+ * "..", save the current directory in prev_dir, so that
+ * we can select it later. */
+ } else if (strcmp(tail(filelist[selected]), "..") == 0)
+ prev_dir = mallocstrcpy(NULL,
+ striponedir(filelist[selected]));
+
+ dir = opendir(filelist[selected]);
+ if (dir == NULL) {
+ /* We can't open this directory for some reason.
+ * Complain. */
+ statusbar(_("Error reading %s: %s"),
+ filelist[selected], strerror(errno));
+ beep();
+ continue;
+ }
+
+ path = mallocstrcpy(path, filelist[selected]);
+
+ /* Start over again with the new path value. */
+ goto change_browser_directory;
+ /* Abort the file browser. */
+ } else if (f->scfunc == do_exit) {
+ abort = TRUE;
+ }
+ }
+ titlebar(NULL);
+ edit_refresh();
+ curs_set(1);
+ if (old_const_update)
+ SET(CONST_UPDATE);
+
+ free(path);
+ free(ans);
+
+ free_chararray(filelist, filelist_len);
+ filelist = NULL;
+ filelist_len = 0;
+
+ return retval;
+}
+
+/* The file browser front end. We check to see if inpath has a
+ * directory in it. If it does, we start do_browser() from there.
+ * Otherwise, we start do_browser() from the current directory. */
+char *do_browse_from(const char *inpath)
+{
+ struct stat st;
+ char *path;
+ /* This holds the tilde-expanded version of inpath. */
+ DIR *dir = NULL;
+
+ assert(inpath != NULL);
+
+ path = real_dir_from_tilde(inpath);
+
+ /* Perhaps path is a directory. If so, we'll pass it to
+ * do_browser(). Or perhaps path is a directory / a file. If so,
+ * we'll try stripping off the last path element and passing it to
+ * do_browser(). Or perhaps path doesn't have a directory portion
+ * at all. If so, we'll just pass the current directory to
+ * do_browser(). */
+ if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ path = mallocstrassn(path, striponedir(path));
+
+ if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ free(path);
+
+ path = charalloc(PATH_MAX + 1);
+ path = getcwd(path, PATH_MAX + 1);
+
+ if (path != NULL)
+ align(&path);
+ }
+ }
+
+#ifndef DISABLE_OPERATINGDIR
+ /* If the resulting path isn't in the operating directory, use
+ * the operating directory instead. */
+ if (check_operating_dir(path, FALSE))
+ path = mallocstrcpy(path, operating_dir);
+#endif
+
+ if (path != NULL)
+ dir = opendir(path);
+
+ /* If we can't open the path, get out. */
+ if (dir == NULL) {
+ if (path != NULL)
+ free(path);
+ beep();
+ return NULL;
+ }
+
+ return do_browser(path, dir);
+}
+
+/* Set filelist to the list of files contained in the directory path,
+ * set filelist_len to the number of files in that list, set longest to
+ * the width in columns of the longest filename in that list (between 15
+ * and COLS), and set width to the number of files that we can display
+ * per line. longest needs to be at least 15 columns in order to
+ * display ".. (parent dir)", as Pico does. Assume path exists and is a
+ * directory. */
+void browser_init(const char *path, DIR *dir)
+{
+ const struct dirent *nextdir;
+ size_t i = 0, path_len = strlen(path);
+ int col = 0;
+ /* The maximum number of columns that the filenames will take
+ * up. */
+ int line = 0;
+ /* The maximum number of lines that the filenames will take
+ * up. */
+ int filesperline = 0;
+ /* The number of files that we can display per line. */
+
+ assert(path != NULL && path[strlen(path) - 1] == '/' && dir != NULL);
+
+ /* Set longest to zero, just before we initialize it. */
+ longest = 0;
+
+ while ((nextdir = readdir(dir)) != NULL) {
+ size_t d_len;
+
+ /* Don't show the "." entry. */
+ if (strcmp(nextdir->d_name, ".") == 0)
+ continue;
+
+ d_len = strlenpt(nextdir->d_name);
+ if (d_len > longest)
+ longest = (d_len > COLS) ? COLS : d_len;
+
+ i++;
+ }
+
+ rewinddir(dir);
+
+ /* Put 10 columns' worth of blank space between columns of filenames
+ * in the list whenever possible, as Pico does. */
+ longest += 10;
+
+ if (filelist != NULL)
+ free_chararray(filelist, filelist_len);
+
+ filelist_len = i;
+
+ filelist = (char **)nmalloc(filelist_len * sizeof(char *));
+
+ i = 0;
+
+ while ((nextdir = readdir(dir)) != NULL && i < filelist_len) {
+ /* Don't show the "." entry. */
+ if (strcmp(nextdir->d_name, ".") == 0)
+ continue;
+
+ filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1);
+ sprintf(filelist[i], "%s%s", path, nextdir->d_name);
+
+ i++;
+ }
+
+ /* Maybe the number of files in the directory changed between the
+ * first time we scanned and the second. i is the actual length of
+ * filelist, so record it. */
+ filelist_len = i;
+
+ closedir(dir);
+
+ /* Make sure longest is between 15 and COLS. */
+ if (longest < 15)
+ longest = 15;
+ if (longest > COLS)
+ longest = COLS;
+
+ /* Set width to zero, just before we initialize it. */
+ width = 0;
+
+ for (i = 0; i < filelist_len && line < editwinrows; i++) {
+ /* Calculate the number of columns one filename will take up. */
+ col += longest;
+ filesperline++;
+
+ /* Add some space between the columns. */
+ col += 2;
+
+ /* If the next entry isn't going to fit on the current line,
+ * move to the next line. */
+ if (col > COLS - longest) {
+ line++;
+ col = 0;
+
+ /* If width isn't initialized yet, and we've taken up more
+ * than one line, it means that width is equal to
+ * filesperline. */
+ if (width == 0)
+ width = filesperline;
+ }
+ }
+
+ /* If width isn't initialized yet, and we've taken up only one line,
+ * it means that width is equal to longest. */
+ if (width == 0)
+ width = longest;
+}
+
+/* Determine the shortcut key corresponding to the values of kbinput
+ * (the key itself), meta_key (whether the key is a meta sequence), and
+ * func_key (whether the key is a function key), if any. In the
+ * process, convert certain non-shortcut keys into their corresponding
+ * shortcut keys. */
+void parse_browser_input(int *kbinput, bool *meta_key, bool *func_key)
+{
+ get_shortcut(MBROWSER, kbinput, meta_key, func_key);
+
+ /* Pico compatibility. */
+ if (!*meta_key) {
+ switch (*kbinput) {
+ case ' ':
+ *kbinput = sc_seq_or(do_page_down, 0);
+ break;
+ case '-':
+ *kbinput = sc_seq_or(do_page_up, 0);
+ break;
+ case '?':
+#ifndef DISABLE_HELP
+ *kbinput = sc_seq_or(do_help_void, 0);
+#endif
+ break;
+ /* Cancel equivalent to Exit here. */
+ case 'E':
+ case 'e':
+ *kbinput = sc_seq_or(do_exit, 0);
+ break;
+ case 'G':
+ case 'g':
+ *kbinput = sc_seq_or(goto_dir_void, 0);
+ break;
+ case 'S':
+ case 's':
+ *kbinput = sc_seq_or(do_enter_void, 0);
+ break;
+ case 'W':
+ case 'w':
+ *kbinput = sc_seq_or(do_search, 0);
+ break;
+ }
+ }
+}
+
+/* Set width to the number of files that we can display per line, if
+ * necessary, and display the list of files. */
+void browser_refresh(void)
+{
+ static int uimax_digits = -1;
+ size_t i;
+ int col = 0;
+ /* The maximum number of columns that the filenames will take
+ * up. */
+ int line = 0;
+ /* The maximum number of lines that the filenames will take
+ * up. */
+ char *foo;
+ /* The file information that we'll display. */
+
+ if (uimax_digits == -1)
+ uimax_digits = digits(UINT_MAX);
+
+ blank_edit();
+
+ wmove(edit, 0, 0);
+
+ i = width * editwinrows * ((selected / width) / editwinrows);
+
+ for (; i < filelist_len && line < editwinrows; i++) {
+ struct stat st;
+ const char *filetail = tail(filelist[i]);
+ /* The filename we display, minus the path. */
+ size_t filetaillen = strlenpt(filetail);
+ /* The length of the filename in columns. */
+ size_t foolen;
+ /* The length of the file information in columns. */
+ int foomaxlen = 7;
+ /* The maximum length of the file information in
+ * columns: seven for "--", "(dir)", or the file size,
+ * and 12 for "(parent dir)". */
+ bool dots = (COLS >= 15 && filetaillen >= longest -
+ foomaxlen - 1);
+ /* Do we put an ellipsis before the filename? Don't set
+ * this to TRUE if we have fewer than 15 columns (i.e.
+ * one column for padding, plus seven columns for a
+ * filename other than ".."). */
+ char *disp = display_string(filetail, dots ? filetaillen -
+ longest + foomaxlen + 4 : 0, longest, FALSE);
+ /* If we put an ellipsis before the filename, reserve
+ * one column for padding, plus seven columns for "--",
+ * "(dir)", or the file size, plus three columns for the
+ * ellipsis. */
+
+ /* Start highlighting the currently selected file or
+ * directory. */
+ if (i == selected)
+ wattron(edit, reverse_attr);
+
+ blank_line(edit, line, col, longest);
+
+ /* If dots is TRUE, we will display something like
+ * "...ename". */
+ if (dots)
+ mvwaddstr(edit, line, col, "...");
+ mvwaddstr(edit, line, dots ? col + 3 : col, disp);
+
+ free(disp);
+
+ col += longest;
+
+ /* Show information about the file. We don't want to report
+ * file sizes for links, so we use lstat(). */
+ if (lstat(filelist[i], &st) == -1 || S_ISLNK(st.st_mode)) {
+ /* If the file doesn't exist (i.e. it's been deleted while
+ * the file browser is open), or it's a symlink that doesn't
+ * point to a directory, display "--". */
+ if (stat(filelist[i], &st) == -1 || !S_ISDIR(st.st_mode))
+ foo = mallocstrcpy(NULL, "--");
+ /* If the file is a symlink that points to a directory,
+ * display it as a directory. */
+ else
+ /* TRANSLATORS: Try to keep this at most 7
+ * characters. */
+ foo = mallocstrcpy(NULL, _("(dir)"));
+ } else if (S_ISDIR(st.st_mode)) {
+ /* If the file is a directory, display it as such. */
+ if (strcmp(filetail, "..") == 0) {
+ /* TRANSLATORS: Try to keep this at most 12
+ * characters. */
+ foo = mallocstrcpy(NULL, _("(parent dir)"));
+ foomaxlen = 12;
+ } else
+ foo = mallocstrcpy(NULL, _("(dir)"));
+ } else {
+ unsigned long result = st.st_size;
+ char modifier;
+
+ foo = charalloc(uimax_digits + 4);
+
+ /* Bytes. */
+ if (st.st_size < (1 << 10))
+ modifier = ' ';
+ /* Kilobytes. */
+ else if (st.st_size < (1 << 20)) {
+ result >>= 10;
+ modifier = 'K';
+ /* Megabytes. */
+ } else if (st.st_size < (1 << 30)) {
+ result >>= 20;
+ modifier = 'M';
+ /* Gigabytes. */
+ } else {
+ result >>= 30;
+ modifier = 'G';
+ }
+
+ sprintf(foo, "%4lu %cB", result, modifier);
+ }
+
+ /* Make sure foo takes up no more than foomaxlen columns. */
+ foolen = strlenpt(foo);
+ if (foolen > foomaxlen) {
+ null_at(&foo, actual_x(foo, foomaxlen));
+ foolen = foomaxlen;
+ }
+
+ mvwaddstr(edit, line, col - foolen, foo);
+
+ /* Finish highlighting the currently selected file or
+ * directory. */
+ if (i == selected)
+ wattroff(edit, reverse_attr);
+
+ free(foo);
+
+ /* Add some space between the columns. */
+ col += 2;
+
+ /* If the next entry isn't going to fit on the current line,
+ * move to the next line. */
+ if (col > COLS - longest) {
+ line++;
+ col = 0;
+ }
+
+ wmove(edit, line, col);
+ }
+
+ wnoutrefresh(edit);
+}
+
+/* Look for needle. If we find it, set selected to its location. Note
+ * that needle must be an exact match for a file in the list. The
+ * return value specifies whether we found anything. */
+bool browser_select_filename(const char *needle)
+{
+ size_t currselected;
+ bool found = FALSE;
+
+ for (currselected = 0; currselected < filelist_len;
+ currselected++) {
+ if (strcmp(filelist[currselected], needle) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ selected = currselected;
+
+ return found;
+}
+
+/* Set up the system variables for a filename search. Return -1 if the
+ * search should be canceled (due to Cancel, a blank search string, or a
+ * failed regcomp()), return 0 on success, and return 1 on rerun calling
+ * program. */
+int filesearch_init(void)
+{
+ int i = 0;
+ char *buf;
+ bool meta_key, func_key;
+ const sc *s;
+ static char *backupstring = NULL;
+ /* The search string we'll be using. */
+
+ /* If backupstring doesn't exist, initialize it to "". */
+ if (backupstring == NULL)
+ backupstring = mallocstrcpy(NULL, "");
+
+ /* We display the search prompt below. If the user types a partial
+ * search string and then Replace or a toggle, we will return to
+ * do_search() or do_replace() and be called again. In that case,
+ * we should put the same search string back up. */
+
+ search_init_globals();
+
+ if (last_search[0] != '\0') {
+ char *disp = display_string(last_search, 0, COLS / 3, FALSE);
+
+ buf = charalloc(strlen(disp) + 7);
+ /* We use (COLS / 3) here because we need to see more on the
+ * line. */
+ sprintf(buf, " [%s%s]", disp,
+ (strlenpt(last_search) > COLS / 3) ? "..." : "");
+ free(disp);
+ } else
+ buf = mallocstrcpy(NULL, "");
+
+ /* This is now one simple call. It just does a lot. */
+ i = do_prompt(FALSE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ MWHEREISFILE, backupstring,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ &search_history,
+#endif
+ browser_refresh, "%s%s%s%s%s%s", _("Search"),
+#ifndef NANO_TINY
+ /* This string is just a modifier for the search prompt; no
+ * grammar is implied. */
+ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
+#endif
+ "",
+#ifdef HAVE_REGEX_H
+ /* This string is just a modifier for the search prompt; no
+ * grammar is implied. */
+ ISSET(USE_REGEXP) ? _(" [Regexp]") :
+#endif
+ "",
+#ifndef NANO_TINY
+ /* This string is just a modifier for the search prompt; no
+ * grammar is implied. */
+ ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
+#endif
+ "", "", buf);
+
+ /* Release buf now that we don't need it anymore. */
+ free(buf);
+
+ free(backupstring);
+ backupstring = NULL;
+
+ /* Cancel any search, or just return with no previous search. */
+ if (i == -1 || (i < 0 && *last_search == '\0') || (i == 0 &&
+ *answer == '\0')) {
+ statusbar(_("Cancelled"));
+ return -1;
+ } else {
+ s = get_shortcut(MBROWSER, &i, &meta_key, &func_key);
+ if (i == -2 || i == 0) {
+#ifdef HAVE_REGEX_H
+ /* Use last_search if answer is an empty string, or
+ * answer if it isn't. */
+ if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ?
+ last_search : answer))
+ return -1;
+#endif
+ } else
+#ifndef NANO_TINY
+ if (s && s->scfunc == case_sens_void) {
+ TOGGLE(CASE_SENSITIVE);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+ } else if (s && s->scfunc == backwards_void) {
+ TOGGLE(BACKWARDS_SEARCH);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+ } else
+#endif
+#ifdef HAVE_REGEX_H
+ if (s && s->scfunc == regexp_void) {
+ TOGGLE(USE_REGEXP);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+ } else
+#endif
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Look for needle. If no_sameline is TRUE, skip over selected when
+ * looking for needle. begin is the location of the filename where we
+ * first started searching. The return value specifies whether we found
+ * anything. */
+bool findnextfile(bool no_sameline, size_t begin, const char *needle)
+{
+ size_t currselected = selected;
+ /* The location in the current file list of the match we
+ * find. */
+ const char *filetail = tail(filelist[currselected]);
+ /* The filename we display, minus the path. */
+ const char *rev_start = filetail, *found = NULL;
+
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH))
+ rev_start += strlen(rev_start);
+#endif
+
+ /* Look for needle in the current filename we're searching. */
+ while (TRUE) {
+ found = strstrwrapper(filetail, needle, rev_start);
+
+ /* We've found a potential match. If we're not allowed to find
+ * a match on the same filename we started on and this potential
+ * match is on that line, continue searching. */
+ if (found != NULL && (!no_sameline || currselected != begin))
+ break;
+
+ /* We've finished processing the filenames, so get out. */
+ if (search_last_file) {
+ not_found_msg(needle);
+ return FALSE;
+ }
+
+ /* Move to the previous or next filename in the list. If we've
+ * reached the start or end of the list, wrap around. */
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH)) {
+ if (currselected > 0)
+ currselected--;
+ else {
+ currselected = filelist_len - 1;
+ statusbar(_("Search Wrapped"));
+ }
+ } else {
+#endif
+ if (currselected < filelist_len - 1)
+ currselected++;
+ else {
+ currselected = 0;
+ statusbar(_("Search Wrapped"));
+ }
+#ifndef NANO_TINY
+ }
+#endif
+
+ /* We've reached the original starting file. */
+ if (currselected == begin)
+ search_last_file = TRUE;
+
+ filetail = tail(filelist[currselected]);
+
+ rev_start = filetail;
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH))
+ rev_start += strlen(rev_start);
+#endif
+ }
+
+ /* We've definitely found something. */
+ selected = currselected;
+
+ return TRUE;
+}
+
+/* Clear the flag indicating that a search reached the last file in the
+ * list. We need to do this just before a new search. */
+void findnextfile_wrap_reset(void)
+{
+ search_last_file = FALSE;
+}
+
+/* Abort the current filename search. Clean up by setting the current
+ * shortcut list to the browser shortcut list, displaying it, and
+ * decompiling the compiled regular expression we used in the last
+ * search, if any. */
+void filesearch_abort(void)
+{
+ currmenu = MBROWSER;
+ bottombars(MBROWSER);
+#ifdef HAVE_REGEX_H
+ regexp_cleanup();
+#endif
+}
+
+/* Search for a filename. */
+void do_filesearch(void)
+{
+ size_t begin = selected;
+ int i;
+ bool didfind;
+
+ i = filesearch_init();
+ if (i == -1) /* Cancel, blank search string, or regcomp()
+ * failed. */
+ filesearch_abort();
+#if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
+ else if (i == 1) /* Case Sensitive, Backwards, or Regexp search
+ * toggle. */
+ do_filesearch();
+#endif
+
+ if (i != 0)
+ return;
+
+ /* If answer is now "", copy last_search into answer. */
+ if (*answer == '\0')
+ answer = mallocstrcpy(answer, last_search);
+ else
+ last_search = mallocstrcpy(last_search, answer);
+
+#ifndef NANO_TINY
+ /* If answer is not "", add this search string to the search history
+ * list. */
+ if (answer[0] != '\0')
+ update_history(&search_history, answer);
+#endif
+
+ findnextfile_wrap_reset();
+ didfind = findnextfile(FALSE, begin, answer);
+
+ /* Check to see if there's only one occurrence of the string and
+ * we're on it now. */
+ if (selected == begin && didfind) {
+ /* Do the search again, skipping over the current line. We
+ * should only end up back at the same position if the string
+ * isn't found again, in which case it's the only occurrence. */
+ didfind = findnextfile(TRUE, begin, answer);
+ if (selected == begin && !didfind)
+ statusbar(_("This is the only occurrence"));
+ }
+
+ filesearch_abort();
+}
+
+/* Search for the last filename without prompting. */
+void do_fileresearch(void)
+{
+ size_t begin = selected;
+ bool didfind;
+
+ search_init_globals();
+
+ if (last_search[0] != '\0') {
+#ifdef HAVE_REGEX_H
+ /* Since answer is "", use last_search! */
+ if (ISSET(USE_REGEXP) && !regexp_init(last_search))
+ return;
+#endif
+
+ findnextfile_wrap_reset();
+ didfind = findnextfile(FALSE, begin, answer);
+
+ /* Check to see if there's only one occurrence of the string and
+ * we're on it now. */
+ if (selected == begin && didfind) {
+ /* Do the search again, skipping over the current line. We
+ * should only end up back at the same position if the
+ * string isn't found again, in which case it's the only
+ * occurrence. */
+ didfind = findnextfile(TRUE, begin, answer);
+ if (selected == begin && !didfind)
+ statusbar(_("This is the only occurrence"));
+ }
+ } else
+ statusbar(_("No current search pattern"));
+
+ filesearch_abort();
+}
+
+/* Select the first file in the list. */
+void do_first_file(void)
+{
+ selected = 0;
+}
+
+/* Select the last file in the list. */
+void do_last_file(void)
+{
+ selected = filelist_len - 1;
+}
+
+/* Strip one directory from the end of path, and return the stripped
+ * path. The returned string is dynamically allocated, and should be
+ * freed. */
+char *striponedir(const char *path)
+{
+ char *retval, *tmp;
+
+ assert(path != NULL);
+
+ retval = mallocstrcpy(NULL, path);
+
+ tmp = strrchr(retval, '/');
+
+ if (tmp != NULL)
+ null_at(&retval, tmp - retval);
+
+ return retval;
+}
+
+#endif /* !DISABLE_BROWSER */
diff --git a/src/chars.c b/src/chars.c
new file mode 100644
index 0000000..e4309b7
--- /dev/null
+++ b/src/chars.c
@@ -0,0 +1,963 @@
+/* $Id: chars.c 4534 2011-02-24 02:47:25Z astyanax $ */
+/**************************************************************************
+ * chars.c *
+ * *
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 *
+ * Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <ctype.h>
+
+#ifdef ENABLE_UTF8
+#ifdef HAVE_WCHAR_H
+#include <wchar.h>
+#endif
+#ifdef HAVE_WCTYPE_H
+#include <wctype.h>
+#endif
+
+static bool use_utf8 = FALSE;
+ /* Whether we've enabled UTF-8 support. */
+static const wchar_t bad_wchar = 0xFFFD;
+ /* If we get an invalid multibyte sequence, we treat it as
+ * Unicode FFFD (Replacement Character), unless we're searching
+ * for a match to it. */
+static const char *const bad_mbchar = "\xEF\xBF\xBD";
+static const int bad_mbchar_len = 3;
+
+/* Enable UTF-8 support. */
+void utf8_init(void)
+{
+ use_utf8 = TRUE;
+}
+
+/* Is UTF-8 support enabled? */
+bool using_utf8(void)
+{
+ return use_utf8;
+}
+#endif
+
+#ifndef HAVE_ISBLANK
+/* This function is equivalent to isblank(). */
+bool nisblank(int c)
+{
+ return isspace(c) && (c == '\t' || !is_cntrl_char(c));
+}
+#endif
+
+#if !defined(HAVE_ISWBLANK) && defined(ENABLE_UTF8)
+/* This function is equivalent to iswblank(). */
+bool niswblank(wchar_t wc)
+{
+ return iswspace(wc) && (wc == '\t' || !is_cntrl_wchar(wc));
+}
+#endif
+
+/* Return TRUE if the value of c is in byte range, and FALSE
+ * otherwise. */
+bool is_byte(int c)
+{
+ return ((unsigned int)c == (unsigned char)c);
+}
+
+static void mbtowc_reset(void)
+{
+ IGNORE_CALL_RESULT(mbtowc(NULL, NULL, 0));
+}
+
+static void wctomb_reset(void)
+{
+ IGNORE_CALL_RESULT(wctomb(NULL, 0));
+}
+
+/* This function is equivalent to isalnum() for multibyte characters. */
+bool is_alnum_mbchar(const char *c)
+{
+ assert(c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0) {
+ mbtowc_reset();
+ wc = bad_wchar;
+ }
+
+ return iswalnum(wc);
+ } else
+#endif
+ return isalnum((unsigned char)*c);
+}
+
+/* This function is equivalent to isblank() for multibyte characters. */
+bool is_blank_mbchar(const char *c)
+{
+ assert(c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0) {
+ mbtowc_reset();
+ wc = bad_wchar;
+ }
+
+ return iswblank(wc);
+ } else
+#endif
+ return isblank((unsigned char)*c);
+}
+
+/* This function is equivalent to iscntrl(), except in that it only
+ * handles non-high-bit control characters. */
+bool is_ascii_cntrl_char(int c)
+{
+ return (0 <= c && c < 32);
+}
+
+/* This function is equivalent to iscntrl(), except in that it also
+ * handles high-bit control characters. */
+bool is_cntrl_char(int c)
+{
+ return (-128 <= c && c < -96) || (0 <= c && c < 32) ||
+ (127 <= c && c < 160);
+}
+
+#ifdef ENABLE_UTF8
+/* This function is equivalent to iscntrl() for wide characters, except
+ * in that it also handles wide control characters with their high bits
+ * set. */
+bool is_cntrl_wchar(wchar_t wc)
+{
+ return (0 <= wc && wc < 32) || (127 <= wc && wc < 160);
+}
+#endif
+
+/* This function is equivalent to iscntrl() for multibyte characters,
+ * except in that it also handles multibyte control characters with
+ * their high bits set. */
+bool is_cntrl_mbchar(const char *c)
+{
+ assert(c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0) {
+ mbtowc_reset();
+ wc = bad_wchar;
+ }
+
+ return is_cntrl_wchar(wc);
+ } else
+#endif
+ return is_cntrl_char((unsigned char)*c);
+}
+
+/* This function is equivalent to ispunct() for multibyte characters. */
+bool is_punct_mbchar(const char *c)
+{
+ assert(c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+ int c_mb_len = mbtowc(&wc, c, MB_CUR_MAX);
+
+ if (c_mb_len < 0) {
+ mbtowc_reset();
+ wc = bad_wchar;
+ }
+
+ return iswpunct(wc);
+ } else
+#endif
+ return ispunct((unsigned char)*c);
+}
+
+/* Return TRUE for a multibyte character found in a word (currently only
+ * an alphanumeric or punctuation character, and only the latter if
+ * allow_punct is TRUE) and FALSE otherwise. */
+bool is_word_mbchar(const char *c, bool allow_punct)
+{
+ assert(c != NULL);
+
+ return is_alnum_mbchar(c) || (allow_punct ? is_punct_mbchar(c) :
+ FALSE);
+}
+
+/* c is a control character. It displays as ^@, ^?, or ^[ch], where ch
+ * is (c + 64). We return that character. */
+char control_rep(char c)
+{
+ assert(is_cntrl_char(c));
+
+ /* Treat newlines embedded in a line as encoded nulls. */
+ if (c == '\n')
+ return '@';
+ else if (c == NANO_CONTROL_8)
+ return '?';
+ else
+ return c + 64;
+}
+
+#ifdef ENABLE_UTF8
+/* c is a wide control character. It displays as ^@, ^?, or ^[ch],
+ * where ch is (c + 64). We return that wide character. */
+wchar_t control_wrep(wchar_t wc)
+{
+ assert(is_cntrl_wchar(wc));
+
+ /* Treat newlines embedded in a line as encoded nulls. */
+ if (wc == '\n')
+ return '@';
+ else if (wc == NANO_CONTROL_8)
+ return '?';
+ else
+ return wc + 64;
+}
+#endif
+
+/* c is a multibyte control character. It displays as ^@, ^?, or ^[ch],
+ * where ch is (c + 64). We return that multibyte character. If crep
+ * is an invalid multibyte sequence, it will be replaced with Unicode
+ * 0xFFFD (Replacement Character). */
+char *control_mbrep(const char *c, char *crep, int *crep_len)
+{
+ assert(c != NULL && crep != NULL && crep_len != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0) {
+ mbtowc_reset();
+ *crep_len = bad_mbchar_len;
+ strncpy(crep, bad_mbchar, *crep_len);
+ } else {
+ *crep_len = wctomb(crep, control_wrep(wc));
+
+ if (*crep_len < 0) {
+ wctomb_reset();
+ *crep_len = 0;
+ }
+ }
+ } else {
+#endif
+ *crep_len = 1;
+ *crep = control_rep(*c);
+#ifdef ENABLE_UTF8
+ }
+#endif
+
+ return crep;
+}
+
+/* c is a multibyte non-control character. We return that multibyte
+ * character. If crep is an invalid multibyte sequence, it will be
+ * replaced with Unicode 0xFFFD (Replacement Character). */
+char *mbrep(const char *c, char *crep, int *crep_len)
+{
+ assert(c != NULL && crep != NULL && crep_len != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+
+ /* Reject invalid Unicode characters. */
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0 || !is_valid_unicode(wc)) {
+ mbtowc_reset();
+ *crep_len = bad_mbchar_len;
+ strncpy(crep, bad_mbchar, *crep_len);
+ } else {
+ *crep_len = wctomb(crep, wc);
+
+ if (*crep_len < 0) {
+ wctomb_reset();
+ *crep_len = 0;
+ }
+ }
+ } else {
+#endif
+ *crep_len = 1;
+ *crep = *c;
+#ifdef ENABLE_UTF8
+ }
+#endif
+
+ return crep;
+}
+
+/* This function is equivalent to wcwidth() for multibyte characters. */
+int mbwidth(const char *c)
+{
+ assert(c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ wchar_t wc;
+ int width;
+
+ if (mbtowc(&wc, c, MB_CUR_MAX) < 0) {
+ mbtowc_reset();
+ wc = bad_wchar;
+ }
+
+ width = wcwidth(wc);
+
+ if (width == -1) {
+ wc = bad_wchar;
+ width = wcwidth(wc);
+ }
+
+ return width;
+ } else
+#endif
+ return 1;
+}
+
+/* Return the maximum width in bytes of a multibyte character. */
+int mb_cur_max(void)
+{
+ return
+#ifdef ENABLE_UTF8
+ use_utf8 ? MB_CUR_MAX :
+#endif
+ 1;
+}
+
+/* Convert the Unicode value in chr to a multibyte character with the
+ * same wide character value as chr, if possible. If the conversion
+ * succeeds, return the (dynamically allocated) multibyte character and
+ * its length. Otherwise, return an undefined (dynamically allocated)
+ * multibyte character and a length of zero. */
+char *make_mbchar(long chr, int *chr_mb_len)
+{
+ char *chr_mb;
+
+ assert(chr_mb_len != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ chr_mb = charalloc(MB_CUR_MAX);
+ *chr_mb_len = wctomb(chr_mb, (wchar_t)chr);
+
+ /* Reject invalid Unicode characters. */
+ if (*chr_mb_len < 0 || !is_valid_unicode((wchar_t)chr)) {
+ wctomb_reset();
+ *chr_mb_len = 0;
+ }
+ } else {
+#endif
+ *chr_mb_len = 1;
+ chr_mb = mallocstrncpy(NULL, (char *)&chr, 1);
+#ifdef ENABLE_UTF8
+ }
+#endif
+
+ return chr_mb;
+}
+
+/* Parse a multibyte character from buf. Return the number of bytes
+ * used. If chr isn't NULL, store the multibyte character in it. If
+ * col isn't NULL, store the new display width in it. If *buf is '\t',
+ * we expect col to have the current display width. */
+int parse_mbchar(const char *buf, char *chr, size_t *col)
+{
+ int buf_mb_len;
+
+ assert(buf != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ /* Get the number of bytes in the multibyte character. */
+ buf_mb_len = mblen(buf, MB_CUR_MAX);
+
+ /* If buf contains an invalid multibyte character, only
+ * interpret buf's first byte. */
+ if (buf_mb_len < 0) {
+ IGNORE_CALL_RESULT(mblen(NULL, 0));
+ buf_mb_len = 1;
+ } else if (buf_mb_len == 0)
+ buf_mb_len++;
+
+ /* Save the multibyte character in chr. */
+ if (chr != NULL) {
+ int i;
+
+ for (i = 0; i < buf_mb_len; i++)
+ chr[i] = buf[i];
+ }
+
+ /* Save the column width of the wide character in col. */
+ if (col != NULL) {
+ /* If we have a tab, get its width in columns using the
+ * current value of col. */
+ if (*buf == '\t')
+ *col += tabsize - *col % tabsize;
+ /* If we have a control character, get its width using one
+ * column for the "^" that will be displayed in front of it,
+ * and the width in columns of its visible equivalent as
+ * returned by control_mbrep(). */
+ else if (is_cntrl_mbchar(buf)) {
+ char *ctrl_buf_mb = charalloc(MB_CUR_MAX);
+ int ctrl_buf_mb_len;
+
+ (*col)++;
+
+ ctrl_buf_mb = control_mbrep(buf, ctrl_buf_mb,
+ &ctrl_buf_mb_len);
+
+ *col += mbwidth(ctrl_buf_mb);
+
+ free(ctrl_buf_mb);
+ /* If we have a normal character, get its width in columns
+ * normally. */
+ } else
+ *col += mbwidth(buf);
+ }
+ } else {
+#endif
+ /* Get the number of bytes in the byte character. */
+ buf_mb_len = 1;
+
+ /* Save the byte character in chr. */
+ if (chr != NULL)
+ *chr = *buf;
+
+ if (col != NULL) {
+ /* If we have a tab, get its width in columns using the
+ * current value of col. */
+ if (*buf == '\t')
+ *col += tabsize - *col % tabsize;
+ /* If we have a control character, it's two columns wide:
+ * one column for the "^" that will be displayed in front of
+ * it, and one column for its visible equivalent as returned
+ * by control_mbrep(). */
+ else if (is_cntrl_char((unsigned char)*buf))
+ *col += 2;
+ /* If we have a normal character, it's one column wide. */
+ else
+ (*col)++;
+ }
+#ifdef ENABLE_UTF8
+ }
+#endif
+
+ return buf_mb_len;
+}
+
+/* Return the index in buf of the beginning of the multibyte character
+ * before the one at pos. */
+size_t move_mbleft(const char *buf, size_t pos)
+{
+ size_t pos_prev = pos;
+
+ assert(buf != NULL && pos <= strlen(buf));
+
+ /* There is no library function to move backward one multibyte
+ * character. Here is the naive, O(pos) way to do it. */
+ while (TRUE) {
+ int buf_mb_len = parse_mbchar(buf + pos - pos_prev, NULL, NULL);
+
+ if (pos_prev <= buf_mb_len)
+ break;
+
+ pos_prev -= buf_mb_len;
+ }
+
+ return pos - pos_prev;
+}
+
+/* Return the index in buf of the beginning of the multibyte character
+ * after the one at pos. */
+size_t move_mbright(const char *buf, size_t pos)
+{
+ return pos + parse_mbchar(buf + pos, NULL, NULL);
+}
+
+#ifndef HAVE_STRCASECMP
+/* This function is equivalent to strcasecmp(). */
+int nstrcasecmp(const char *s1, const char *s2)
+{
+ return strncasecmp(s1, s2, (size_t)-1);
+}
+#endif
+
+/* This function is equivalent to strcasecmp() for multibyte strings. */
+int mbstrcasecmp(const char *s1, const char *s2)
+{
+ return mbstrncasecmp(s1, s2, (size_t)-1);
+}
+
+#ifndef HAVE_STRNCASECMP
+/* This function is equivalent to strncasecmp(). */
+int nstrncasecmp(const char *s1, const char *s2, size_t n)
+{
+ if (s1 == s2)
+ return 0;
+
+ assert(s1 != NULL && s2 != NULL);
+
+ for (; *s1 != '\0' && *s2 != '\0' && n > 0; s1++, s2++, n--) {
+ if (tolower(*s1) != tolower(*s2))
+ break;
+ }
+
+ return (n > 0) ? tolower(*s1) - tolower(*s2) : 0;
+}
+#endif
+
+/* This function is equivalent to strncasecmp() for multibyte
+ * strings. */
+int mbstrncasecmp(const char *s1, const char *s2, size_t n)
+{
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ char *s1_mb, *s2_mb;
+ wchar_t ws1, ws2;
+
+ if (s1 == s2)
+ return 0;
+
+ assert(s1 != NULL && s2 != NULL);
+
+ s1_mb = charalloc(MB_CUR_MAX);
+ s2_mb = charalloc(MB_CUR_MAX);
+
+ for (; *s1 != '\0' && *s2 != '\0' && n > 0; s1 +=
+ move_mbright(s1, 0), s2 += move_mbright(s2, 0), n--) {
+ bool bad_s1_mb = FALSE, bad_s2_mb = FALSE;
+ int s1_mb_len, s2_mb_len;
+
+ s1_mb_len = parse_mbchar(s1, s1_mb, NULL);
+
+ if (mbtowc(&ws1, s1_mb, s1_mb_len) < 0) {
+ mbtowc_reset();
+ ws1 = (unsigned char)*s1_mb;
+ bad_s1_mb = TRUE;
+ }
+
+ s2_mb_len = parse_mbchar(s2, s2_mb, NULL);
+
+ if (mbtowc(&ws2, s2_mb, s2_mb_len) < 0) {
+ mbtowc_reset();
+ ws2 = (unsigned char)*s2_mb;
+ bad_s2_mb = TRUE;
+ }
+
+ if (bad_s1_mb != bad_s2_mb || towlower(ws1) !=
+ towlower(ws2))
+ break;
+ }
+
+ free(s1_mb);
+ free(s2_mb);
+
+ return (n > 0) ? towlower(ws1) - towlower(ws2) : 0;
+ } else
+#endif
+ return strncasecmp(s1, s2, n);
+}
+
+#ifndef HAVE_STRCASESTR
+/* This function is equivalent to strcasestr(). */
+char *nstrcasestr(const char *haystack, const char *needle)
+{
+ size_t haystack_len, needle_len;
+
+ assert(haystack != NULL && needle != NULL);
+
+ if (*needle == '\0')
+ return (char *)haystack;
+
+ haystack_len = strlen(haystack);
+ needle_len = strlen(needle);
+
+ for (; *haystack != '\0' && haystack_len >= needle_len; haystack++,
+ haystack_len--) {
+ if (strncasecmp(haystack, needle, needle_len) == 0)
+ return (char *)haystack;
+ }
+
+ return NULL;
+}
+#endif
+
+/* This function is equivalent to strcasestr() for multibyte strings. */
+char *mbstrcasestr(const char *haystack, const char *needle)
+{
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ size_t haystack_len, needle_len;
+
+ assert(haystack != NULL && needle != NULL);
+
+ if (*needle == '\0')
+ return (char *)haystack;
+
+ haystack_len = mbstrlen(haystack);
+ needle_len = mbstrlen(needle);
+
+ for (; *haystack != '\0' && haystack_len >= needle_len;
+ haystack += move_mbright(haystack, 0), haystack_len--) {
+ if (mbstrncasecmp(haystack, needle, needle_len) == 0)
+ return (char *)haystack;
+ }
+
+ return NULL;
+ } else
+#endif
+ return (char *) strcasestr(haystack, needle);
+}
+
+#if !defined(NANO_TINY) || !defined(DISABLE_TABCOMP)
+/* This function is equivalent to strstr(), except in that it scans the
+ * string in reverse, starting at rev_start. */
+char *revstrstr(const char *haystack, const char *needle, const char
+ *rev_start)
+{
+ size_t rev_start_len, needle_len;
+
+ assert(haystack != NULL && needle != NULL && rev_start != NULL);
+
+ if (*needle == '\0')
+ return (char *)rev_start;
+
+ needle_len = strlen(needle);
+
+ if (strlen(haystack) < needle_len)
+ return NULL;
+
+ rev_start_len = strlen(rev_start);
+
+ for (; rev_start >= haystack; rev_start--, rev_start_len++) {
+ if (rev_start_len >= needle_len && strncmp(rev_start, needle,
+ needle_len) == 0)
+ return (char *)rev_start;
+ }
+
+ return NULL;
+}
+#endif /* !NANO_TINY || !DISABLE_TABCOMP */
+
+#ifndef NANO_TINY
+/* This function is equivalent to strcasestr(), except in that it scans
+ * the string in reverse, starting at rev_start. */
+char *revstrcasestr(const char *haystack, const char *needle, const char
+ *rev_start)
+{
+ size_t rev_start_len, needle_len;
+
+ assert(haystack != NULL && needle != NULL && rev_start != NULL);
+
+ if (*needle == '\0')
+ return (char *)rev_start;
+
+ needle_len = strlen(needle);
+
+ if (strlen(haystack) < needle_len)
+ return NULL;
+
+ rev_start_len = strlen(rev_start);
+
+ for (; rev_start >= haystack; rev_start--, rev_start_len++) {
+ if (rev_start_len >= needle_len && strncasecmp(rev_start,
+ needle, needle_len) == 0)
+ return (char *)rev_start;
+ }
+
+ return NULL;
+}
+
+/* This function is equivalent to strcasestr() for multibyte strings,
+ * except in that it scans the string in reverse, starting at
+ * rev_start. */
+char *mbrevstrcasestr(const char *haystack, const char *needle, const
+ char *rev_start)
+{
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ bool begin_line = FALSE;
+ size_t rev_start_len, needle_len;
+
+ assert(haystack != NULL && needle != NULL && rev_start != NULL);
+
+ if (*needle == '\0')
+ return (char *)rev_start;
+
+ needle_len = mbstrlen(needle);
+
+ if (mbstrlen(haystack) < needle_len)
+ return NULL;
+
+ rev_start_len = mbstrlen(rev_start);
+
+ while (!begin_line) {
+ if (rev_start_len >= needle_len && mbstrncasecmp(rev_start,
+ needle, needle_len) == 0)
+ return (char *)rev_start;
+
+ if (rev_start == haystack)
+ begin_line = TRUE;
+ else {
+ rev_start = haystack + move_mbleft(haystack, rev_start -
+ haystack);
+ rev_start_len++;
+ }
+ }
+
+ return NULL;
+ } else
+#endif
+ return revstrcasestr(haystack, needle, rev_start);
+}
+#endif /* !NANO_TINY */
+
+/* This function is equivalent to strlen() for multibyte strings. */
+size_t mbstrlen(const char *s)
+{
+ return mbstrnlen(s, (size_t)-1);
+}
+
+#ifndef HAVE_STRNLEN
+/* This function is equivalent to strnlen(). */
+size_t nstrnlen(const char *s, size_t maxlen)
+{
+ size_t n = 0;
+
+ assert(s != NULL);
+
+ for (; *s != '\0' && maxlen > 0; s++, maxlen--, n++)
+ ;
+
+ return n;
+}
+#endif
+
+/* This function is equivalent to strnlen() for multibyte strings. */
+size_t mbstrnlen(const char *s, size_t maxlen)
+{
+ assert(s != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ size_t n = 0;
+
+ for (; *s != '\0' && maxlen > 0; s += move_mbright(s, 0),
+ maxlen--, n++)
+ ;
+
+ return n;
+ } else
+#endif
+ return strnlen(s, maxlen);
+}
+
+#if !defined(NANO_TINY) || !defined(DISABLE_JUSTIFY)
+/* This function is equivalent to strchr() for multibyte strings. */
+char *mbstrchr(const char *s, const char *c)
+{
+ assert(s != NULL && c != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ bool bad_s_mb = FALSE, bad_c_mb = FALSE;
+ char *s_mb = charalloc(MB_CUR_MAX);
+ const char *q = s;
+ wchar_t ws, wc;
+ int c_mb_len = mbtowc(&wc, c, MB_CUR_MAX);
+
+ if (c_mb_len < 0) {
+ mbtowc_reset();
+ wc = (unsigned char)*c;
+ bad_c_mb = TRUE;
+ }
+
+ while (*s != '\0') {
+ int s_mb_len = parse_mbchar(s, s_mb, NULL);
+
+ if (mbtowc(&ws, s_mb, s_mb_len) < 0) {
+ mbtowc_reset();
+ ws = (unsigned char)*s;
+ bad_s_mb = TRUE;
+ }
+
+ if (bad_s_mb == bad_c_mb && ws == wc)
+ break;
+
+ s += s_mb_len;
+ q += s_mb_len;
+ }
+
+ free(s_mb);
+
+ if (*s == '\0')
+ q = NULL;
+
+ return (char *)q;
+ } else
+#endif
+ return (char *) strchr(s, *c);
+}
+#endif /* !NANO_TINY || !DISABLE_JUSTIFY */
+
+#ifndef NANO_TINY
+/* This function is equivalent to strpbrk() for multibyte strings. */
+char *mbstrpbrk(const char *s, const char *accept)
+{
+ assert(s != NULL && accept != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ for (; *s != '\0'; s += move_mbright(s, 0)) {
+ if (mbstrchr(accept, s) != NULL)
+ return (char *)s;
+ }
+
+ return NULL;
+ } else
+#endif
+ return (char *) strpbrk(s, accept);
+}
+
+/* This function is equivalent to strpbrk(), except in that it scans the
+ * string in reverse, starting at rev_start. */
+char *revstrpbrk(const char *s, const char *accept, const char
+ *rev_start)
+{
+ assert(s != NULL && accept != NULL && rev_start != NULL);
+
+ for (; rev_start >= s; rev_start--) {
+ const char *q = (*rev_start == '\0') ? NULL : strchr(accept,
+ *rev_start);
+
+ if (q != NULL)
+ return (char *)rev_start;
+ }
+
+ return NULL;
+}
+
+/* This function is equivalent to strpbrk() for multibyte strings,
+ * except in that it scans the string in reverse, starting at
+ * rev_start. */
+char *mbrevstrpbrk(const char *s, const char *accept, const char
+ *rev_start)
+{
+ assert(s != NULL && accept != NULL && rev_start != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ bool begin_line = FALSE;
+
+ while (!begin_line) {
+ const char *q = (*rev_start == '\0') ? NULL :
+ mbstrchr(accept, rev_start);
+
+ if (q != NULL)
+ return (char *)rev_start;
+
+ if (rev_start == s)
+ begin_line = TRUE;
+ else
+ rev_start = s + move_mbleft(s, rev_start - s);
+ }
+
+ return NULL;
+ } else
+#endif
+ return revstrpbrk(s, accept, rev_start);
+}
+#endif /* !NANO_TINY */
+
+#if defined(ENABLE_NANORC) && (!defined(NANO_TINY) || !defined(DISABLE_JUSTIFY))
+/* Return TRUE if the string s contains one or more blank characters,
+ * and FALSE otherwise. */
+bool has_blank_chars(const char *s)
+{
+ assert(s != NULL);
+
+ for (; *s != '\0'; s++) {
+ if (isblank(*s))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Return TRUE if the multibyte string s contains one or more blank
+ * multibyte characters, and FALSE otherwise. */
+bool has_blank_mbchars(const char *s)
+{
+ assert(s != NULL);
+
+#ifdef ENABLE_UTF8
+ if (use_utf8) {
+ bool retval = FALSE;
+ char *chr_mb = charalloc(MB_CUR_MAX);
+
+ for (; *s != '\0'; s += move_mbright(s, 0)) {
+ parse_mbchar(s, chr_mb, NULL);
+
+ if (is_blank_mbchar(chr_mb)) {
+ retval = TRUE;
+ break;
+ }
+ }
+
+ free(chr_mb);
+
+ return retval;
+ } else
+#endif
+ return has_blank_chars(s);
+}
+#endif /* ENABLE_NANORC && (!NANO_TINY || !DISABLE_JUSTIFY) */
+
+#ifdef ENABLE_UTF8
+/* Return TRUE if wc is valid Unicode, and FALSE otherwise. */
+bool is_valid_unicode(wchar_t wc)
+{
+ return ((0 <= wc && wc <= 0x10FFFF) && (wc <= 0xD7FF || 0xE000 <=
+ wc) && (wc <= 0xFDCF || 0xFDF0 <= wc) && ((wc & 0xFFFF) <=
+ 0xFFFD));
+}
+#endif
+
+#ifdef ENABLE_NANORC
+/* Check if the string s is a valid multibyte string. Return TRUE if it
+ * is, and FALSE otherwise. */
+bool is_valid_mbstring(const char *s)
+{
+ assert(s != NULL);
+
+ return
+#ifdef ENABLE_UTF8
+ use_utf8 ? (mbstowcs(NULL, s, 0) != (size_t)-1) :
+#endif
+ TRUE;
+}
+#endif /* ENABLE_NANORC */
diff --git a/src/color.c b/src/color.c
new file mode 100644
index 0000000..e12efdd
--- /dev/null
+++ b/src/color.c
@@ -0,0 +1,431 @@
+/* $Id: color.c 4540 2011-03-05 05:01:13Z astyanax $ */
+/**************************************************************************
+ * color.c *
+ * *
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 *
+ * Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_MAGIC_H
+#include <magic.h>
+#endif
+
+#ifdef ENABLE_COLOR
+
+/* For each syntax list entry, go through the list of colors and assign
+ * the color pairs. */
+void set_colorpairs(void)
+{
+ const syntaxtype *this_syntax = syntaxes;
+
+ for (; this_syntax != NULL; this_syntax = this_syntax->next) {
+ colortype *this_color = this_syntax->color;
+ int color_pair = 1;
+
+ for (; this_color != NULL; this_color = this_color->next) {
+ const colortype *beforenow = this_syntax->color;
+
+ for (; beforenow != this_color &&
+ (beforenow->fg != this_color->fg ||
+ beforenow->bg != this_color->bg ||
+ beforenow->bright != this_color->bright);
+ beforenow = beforenow->next)
+ ;
+
+ if (beforenow != this_color)
+ this_color->pairnum = beforenow->pairnum;
+ else {
+ this_color->pairnum = color_pair;
+ color_pair++;
+ }
+ }
+ }
+}
+
+/* Initialize the color information. */
+void color_init(void)
+{
+ assert(openfile != NULL);
+
+ if (has_colors()) {
+ const colortype *tmpcolor;
+#ifdef HAVE_USE_DEFAULT_COLORS
+ bool defok;
+#endif
+
+ start_color();
+
+#ifdef HAVE_USE_DEFAULT_COLORS
+ /* Use the default colors, if available. */
+ defok = (use_default_colors() != ERR);
+#endif
+
+ for (tmpcolor = openfile->colorstrings; tmpcolor != NULL;
+ tmpcolor = tmpcolor->next) {
+ short foreground = tmpcolor->fg, background = tmpcolor->bg;
+ if (foreground == -1) {
+#ifdef HAVE_USE_DEFAULT_COLORS
+ if (!defok)
+#endif
+ foreground = COLOR_WHITE;
+ }
+
+ if (background == -1) {
+#ifdef HAVE_USE_DEFAULT_COLORS
+ if (!defok)
+#endif
+ background = COLOR_BLACK;
+ }
+
+ init_pair(tmpcolor->pairnum, foreground, background);
+
+#ifdef DEBUG
+ fprintf(stderr, "init_pair(): fg = %hd, bg = %hd\n", tmpcolor->fg, tmpcolor->bg);
+#endif
+ }
+ }
+}
+
+/* Cleanup a regex we previously compiled */
+void nfreeregex(regex_t **r)
+{
+ assert(r != NULL);
+
+ regfree(*r);
+ free(*r);
+ *r = NULL;
+}
+
+/* Update the color information based on the current filename. */
+void color_update(void)
+{
+ syntaxtype *tmpsyntax;
+ syntaxtype *defsyntax = NULL;
+ colortype *tmpcolor, *defcolor = NULL;
+ exttype *e;
+
+/* libmagic structures */
+/* magicstring will be NULL if we fail to get magic result */
+#ifdef HAVE_LIBMAGIC
+ const char *magicstring = NULL;
+ const char *magicerr = NULL;
+ magic_t m;
+ struct stat fileinfo;
+#endif /* HAVE_LIBMAGIC */
+
+
+ assert(openfile != NULL);
+
+ openfile->syntax = NULL;
+ openfile->colorstrings = NULL;
+
+ /* If we specified a syntax override string, use it. */
+ if (syntaxstr != NULL) {
+ /* If the syntax override is "none", it's the same as not having
+ * a syntax at all, so get out. */
+ if (strcmp(syntaxstr, "none") == 0)
+ return;
+
+ for (tmpsyntax = syntaxes; tmpsyntax != NULL;
+ tmpsyntax = tmpsyntax->next) {
+ if (strcmp(tmpsyntax->desc, syntaxstr) == 0) {
+ openfile->syntax = tmpsyntax;
+ openfile->colorstrings = tmpsyntax->color;
+ }
+
+ if (openfile->colorstrings != NULL)
+ break;
+ }
+ }
+
+#ifdef HAVE_LIBMAGIC
+
+ if (strcmp(openfile->filename,"") && stat(openfile->filename, &fileinfo) == 0) {
+ m = magic_open(MAGIC_SYMLINK |
+#ifdef DEBUG
+ MAGIC_DEBUG | MAGIC_CHECK |
+#endif /* DEBUG */
+ MAGIC_ERROR);
+ if (m == NULL || magic_load(m, NULL) < 0)
+ fprintf(stderr, "something went wrong: %s [%s]\n", strerror(errno), openfile->filename);
+ else {
+ magicstring = magic_file(m,openfile->filename);
+ if (magicstring == NULL) {
+ magicerr = magic_error(m);
+ fprintf(stderr, "something went wrong: %s [%s]\n", magicerr, openfile->filename);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "magic string returned: %s\n", magicstring);
+#endif /* DEBUG */
+ }
+ }
+#endif /* HAVE_LIBMAGIC */
+
+ /* If we didn't specify a syntax override string, or if we did and
+ * there was no syntax by that name, get the syntax based on the
+ * file extension, and then look in the header. */
+ if (openfile->colorstrings == NULL) {
+ for (tmpsyntax = syntaxes; tmpsyntax != NULL;
+ tmpsyntax = tmpsyntax->next) {
+
+ /* If this is the default syntax, it has no associated
+ * extensions, which we've checked for elsewhere. Skip over
+ * it here, but keep track of its color regexes. */
+ if (strcmp(tmpsyntax->desc, "default") == 0) {
+ defsyntax = tmpsyntax;
+ defcolor = tmpsyntax->color;
+ continue;
+ }
+
+ for (e = tmpsyntax->extensions; e != NULL; e = e->next) {
+ bool not_compiled = (e->ext == NULL);
+
+ /* e->ext_regex has already been checked for validity
+ * elsewhere. Compile its specified regex if we haven't
+ * already. */
+ if (not_compiled) {
+ e->ext = (regex_t *)nmalloc(sizeof(regex_t));
+ regcomp(e->ext, fixbounds(e->ext_regex), REG_EXTENDED);
+ }
+
+ /* Set colorstrings if we matched the extension
+ * regex. */
+ if (regexec(e->ext, openfile->filename, 0, NULL, 0) == 0) {
+ openfile->syntax = tmpsyntax;
+ openfile->colorstrings = tmpsyntax->color;
+ break;
+ }
+
+ /* Decompile e->ext_regex's specified regex if we aren't
+ * going to use it. */
+ if (not_compiled)
+ nfreeregex(&e->ext);
+ }
+ }
+
+ /* Check magic if we don't yet have an answer */
+#ifdef HAVE_LIBMAGIC
+ if (openfile->colorstrings == NULL) {
+
+#ifdef DEBUG
+ fprintf(stderr, "No match using extension, trying libmagic...\n");
+#endif /* DEBUG */
+
+ for (tmpsyntax = syntaxes; tmpsyntax != NULL;
+ tmpsyntax = tmpsyntax->next) {
+ for (e = tmpsyntax->magics; e != NULL; e = e->next) {
+ bool not_compiled = (e->ext == NULL);
+ if (not_compiled) {
+ e->ext = (regex_t *)nmalloc(sizeof(regex_t));
+ regcomp(e->ext, fixbounds(e->ext_regex), REG_EXTENDED);
+ }
+#ifdef DEBUG
+ fprintf(stderr,"Matching regex \"%s\" against \"%s\"\n",e->ext_regex, magicstring);
+#endif /* DEBUG */
+
+ if (magicstring && regexec(e->ext, magicstring, 0, NULL, 0) == 0) {
+ openfile->syntax = tmpsyntax;
+ openfile->colorstrings = tmpsyntax->color;
+ break;
+ }
+
+ if (not_compiled)
+ nfreeregex(&e->ext);
+ }
+ }
+ }
+#endif /* HAVE_LIBMAGIC */
+
+ /* If we haven't matched anything yet, try the headers */
+ if (openfile->colorstrings == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "No match for file extensions, looking at headers...\n");
+#endif
+ for (tmpsyntax = syntaxes; tmpsyntax != NULL;
+ tmpsyntax = tmpsyntax->next) {
+
+ for (e = tmpsyntax->headers; e != NULL; e = e->next) {
+ bool not_compiled = (e->ext == NULL);
+
+ /* e->ext_regex has already been checked for validity
+ * elsewhere. Compile its specified regex if we haven't
+ * already. */
+ if (not_compiled) {
+ e->ext = (regex_t *)nmalloc(sizeof(regex_t));
+ regcomp(e->ext, fixbounds(e->ext_regex), REG_EXTENDED);
+ }
+
+ /* Set colorstrings if we matched the extension
+ * regex. */
+#ifdef DEBUG
+ fprintf(stderr, "Comparing header regex \"%s\" to fileage \"%s\"...\n", e->ext_regex, openfile->fileage->data);
+#endif
+ if (regexec(e->ext, openfile->fileage->data, 0, NULL, 0) == 0) {
+ openfile->syntax = tmpsyntax;
+ openfile->colorstrings = tmpsyntax->color;
+ }
+
+ if (openfile->colorstrings != NULL)
+ break;
+
+ /* Decompile e->ext_regex's specified regex if we aren't
+ * going to use it. */
+ if (not_compiled)
+ nfreeregex(&e->ext);
+ }
+ }
+ }
+ }
+
+
+ /* If we didn't get a syntax based on the file extension, and we
+ * have a default syntax, use it. */
+ if (openfile->colorstrings == NULL && defcolor != NULL) {
+ openfile->syntax = defsyntax;
+ openfile->colorstrings = defcolor;
+ }
+
+ for (tmpcolor = openfile->colorstrings; tmpcolor != NULL;
+ tmpcolor = tmpcolor->next) {
+ /* tmpcolor->start_regex and tmpcolor->end_regex have already
+ * been checked for validity elsewhere. Compile their specified
+ * regexes if we haven't already. */
+ if (tmpcolor->start == NULL) {
+ tmpcolor->start = (regex_t *)nmalloc(sizeof(regex_t));
+ regcomp(tmpcolor->start, fixbounds(tmpcolor->start_regex),
+ REG_EXTENDED | (tmpcolor->icase ? REG_ICASE : 0));
+ }
+
+ if (tmpcolor->end_regex != NULL && tmpcolor->end == NULL) {
+ tmpcolor->end = (regex_t *)nmalloc(sizeof(regex_t));
+ regcomp(tmpcolor->end, fixbounds(tmpcolor->end_regex),
+ REG_EXTENDED | (tmpcolor->icase ? REG_ICASE : 0));
+ }
+ }
+}
+
+/* Reset the multicolor info cache for records for any lines which need
+ to be recalculated */
+void reset_multis_after(filestruct *fileptr, int mindex)
+{
+ filestruct *oof;
+ for (oof = fileptr->next; oof != NULL; oof = oof->next) {
+ alloc_multidata_if_needed(oof);
+ if (oof->multidata == NULL)
+ continue;
+ if (oof->multidata[mindex] != CNONE)
+ oof->multidata[mindex] = -1;
+ else
+ break;
+ }
+ for (; oof != NULL; oof = oof->next) {
+ alloc_multidata_if_needed(oof);
+ if (oof->multidata == NULL)
+ continue;
+ if (oof->multidata[mindex] == CNONE)
+ oof->multidata[mindex] = -1;
+ else
+ break;
+ }
+ edit_refresh_needed = TRUE;
+}
+
+void reset_multis_before(filestruct *fileptr, int mindex)
+{
+ filestruct *oof;
+ for (oof = fileptr->prev; oof != NULL; oof = oof->prev) {
+ alloc_multidata_if_needed(oof);
+ if (oof->multidata == NULL)
+ continue;
+ if (oof->multidata[mindex] != CNONE)
+ oof->multidata[mindex] = -1;
+ else
+ break;
+ }
+ for (; oof != NULL; oof = oof->prev) {
+ alloc_multidata_if_needed(oof);
+ if (oof->multidata == NULL)
+ continue;
+ if (oof->multidata[mindex] == CNONE)
+ oof->multidata[mindex] = -1;
+ else
+ break;
+ }
+
+ edit_refresh_needed = TRUE;
+}
+
+/* Reset one multiline regex info */
+void reset_multis_for_id(filestruct *fileptr, int num)
+{
+ reset_multis_before(fileptr, num);
+ reset_multis_after(fileptr, num);
+ fileptr->multidata[num] = -1;
+}
+
+/* Reset multi line strings around a filestruct ptr, trying to be smart about stopping
+ force = reset everything regardless, useful when we don't know how much screen state
+ has changed */
+void reset_multis(filestruct *fileptr, bool force)
+{
+ int nobegin, noend;
+ regmatch_t startmatch, endmatch;
+ const colortype *tmpcolor = openfile->colorstrings;
+
+ if (!openfile->syntax)
+ return;
+
+ for (; tmpcolor != NULL; tmpcolor = tmpcolor->next) {
+
+ /* If it's not a multi-line regex, amscray */
+ if (tmpcolor->end == NULL)
+ continue;
+
+ alloc_multidata_if_needed(fileptr);
+ if (force == TRUE) {
+ reset_multis_for_id(fileptr, tmpcolor->id);
+ continue;
+ }
+
+ /* Figure out where the first begin and end are to determine if
+ things changed drastically for the precalculated multi values */
+ nobegin = regexec(tmpcolor->start, fileptr->data, 1, &startmatch, 0);
+ noend = regexec(tmpcolor->end, fileptr->data, 1, &endmatch, 0);
+ if (fileptr->multidata[tmpcolor->id] == CWHOLELINE) {
+ if (nobegin && noend)
+ continue;
+ } else if (fileptr->multidata[tmpcolor->id] == CNONE) {
+ if (nobegin && noend)
+ continue;
+ } else if (fileptr->multidata[tmpcolor->id] & CBEGINBEFORE && !noend
+ && (nobegin || endmatch.rm_eo > startmatch.rm_eo)) {
+ reset_multis_after(fileptr, tmpcolor->id);
+ continue;
+ }
+
+ /* If we got here assume the worst */
+ reset_multis_for_id(fileptr, tmpcolor->id);
+ }
+}
+#endif /* ENABLE_COLOR */
diff --git a/src/cut.c b/src/cut.c
new file mode 100644
index 0000000..8c21c8e
--- /dev/null
+++ b/src/cut.c
@@ -0,0 +1,291 @@
+/* $Id: cut.c 4453 2009-12-02 03:36:22Z astyanax $ */
+/**************************************************************************
+ * cut.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <stdio.h>
+
+static bool keep_cutbuffer = FALSE;
+ /* Should we keep the contents of the cutbuffer? */
+ /* Pointer to the end of the cutbuffer. */
+
+/* Indicate that we should no longer keep the contents of the
+ * cutbuffer. */
+void cutbuffer_reset(void)
+{
+ keep_cutbuffer = FALSE;
+}
+
+/* If we aren't on the last line of the file, move all the text of the
+ * current line, plus the newline at the end, into the cutbuffer. If we
+ * are, move all of the text of the current line into the cutbuffer. In
+ * both cases, set the current place we want to the beginning of the
+ * current line. */
+void cut_line(void)
+{
+ if (openfile->current != openfile->filebot)
+ move_to_filestruct(&cutbuffer, &cutbottom, openfile->current, 0,
+ openfile->current->next, 0);
+ else
+ move_to_filestruct(&cutbuffer, &cutbottom, openfile->current, 0,
+ openfile->current, strlen(openfile->current->data));
+ openfile->placewewant = 0;
+}
+
+#ifndef NANO_TINY
+/* Move all currently marked text into the cutbuffer, and set the
+ * current place we want to where the text used to start. */
+void cut_marked(void)
+{
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, NULL);
+
+ move_to_filestruct(&cutbuffer, &cutbottom, top, top_x, bot, bot_x);
+ openfile->placewewant = xplustabs();
+}
+
+/* If we aren't at the end of the current line, move all the text from
+ * the current cursor position to the end of the current line, not
+ * counting the newline at the end, into the cutbuffer. If we are, and
+ * we're not on the last line of the file, move the newline at the end
+ * into the cutbuffer, and set the current place we want to where the
+ * newline used to be. */
+void cut_to_eol(void)
+{
+ size_t data_len = strlen(openfile->current->data);
+
+ assert(openfile->current_x <= data_len);
+
+ if (openfile->current_x < data_len)
+ /* If we're not at the end of the line, move all the text from
+ * the current position up to it, not counting the newline at
+ * the end, into the cutbuffer. */
+ move_to_filestruct(&cutbuffer, &cutbottom, openfile->current,
+ openfile->current_x, openfile->current, data_len);
+ else if (openfile->current != openfile->filebot) {
+ /* If we're at the end of the line, and it isn't the last line
+ * of the file, move all the text from the current position up
+ * to the beginning of the next line, i.e. the newline at the
+ * end, into the cutbuffer. */
+ move_to_filestruct(&cutbuffer, &cutbottom, openfile->current,
+ openfile->current_x, openfile->current->next, 0);
+ openfile->placewewant = xplustabs();
+ }
+}
+
+/* Move all the text from the current cursor position to the end of the
+ * file into the cutbuffer. */
+void cut_to_eof(void)
+{
+ move_to_filestruct(&cutbuffer, &cutbottom, openfile->current,
+ openfile->current_x, openfile->filebot,
+ strlen(openfile->filebot->data));
+}
+#endif /* !NANO_TINY */
+
+/* Move text from the current filestruct into the cutbuffer. If
+ * copy_text is TRUE, copy the text back into the filestruct afterward.
+ * If cut_till_end is TRUE, move all text from the current cursor
+ * position to the end of the file into the cutbuffer. */
+void do_cut_text(
+#ifndef NANO_TINY
+ bool copy_text, bool cut_till_end, bool undoing
+#else
+ void
+#endif
+ )
+{
+#ifndef NANO_TINY
+ filestruct *cb_save = NULL;
+ /* The current end of the cutbuffer, before we add text to
+ * it. */
+ size_t cb_save_len = 0;
+ /* The length of the string at the current end of the cutbuffer,
+ * before we add text to it. */
+ bool old_no_newlines = ISSET(NO_NEWLINES);
+#endif
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ /* If keep_cutbuffer is FALSE and the cutbuffer isn't empty, blow
+ * away the text in the cutbuffer. */
+ if (!keep_cutbuffer && cutbuffer != NULL) {
+ free_filestruct(cutbuffer);
+ cutbuffer = NULL;
+#ifdef DEBUG
+ fprintf(stderr, "Blew away cutbuffer =)\n");
+#endif
+ }
+
+#ifndef NANO_TINY
+ if (copy_text) {
+ if (cutbuffer != NULL) {
+ /* If the cutbuffer isn't empty, save where it currently
+ * ends. This is where we'll add the new text. */
+ cb_save = cutbottom;
+ cb_save_len = strlen(cutbottom->data);
+ }
+
+ /* Set NO_NEWLINES to TRUE, so that we don't disturb the last
+ * line of the file when moving text to the cutbuffer. */
+ SET(NO_NEWLINES);
+ }
+#endif
+
+ /* Set keep_cutbuffer to TRUE, so that the text we're going to move
+ * into the cutbuffer will be added to the text already in the
+ * cutbuffer instead of replacing it. */
+ keep_cutbuffer = TRUE;
+
+#ifndef NANO_TINY
+
+ if (cut_till_end) {
+ /* If cut_till_end is TRUE, move all text up to the end of the
+ * file into the cutbuffer. */
+ cut_to_eof();
+ } else if (openfile->mark_set) {
+ /* If the mark is on, move the marked text to the cutbuffer, and
+ * turn the mark off. */
+ cut_marked();
+ openfile->mark_set = FALSE;
+ } else if (ISSET(CUT_TO_END))
+ /* If the CUT_TO_END flag is set, move all text up to the end of
+ * the line into the cutbuffer. */
+ cut_to_eol();
+ else
+#endif
+ /* Move the entire line into the cutbuffer. */
+ cut_line();
+
+#ifndef NANO_TINY
+ if (copy_text) {
+ /* Copy the text in the cutbuffer, starting at its saved end if
+ * there is one, back into the filestruct. This effectively
+ * uncuts the text we just cut without marking the file as
+ * modified. */
+ if (cutbuffer != NULL) {
+ if (cb_save != NULL) {
+ cb_save->data += cb_save_len;
+ copy_from_filestruct(cb_save, cutbottom);
+ cb_save->data -= cb_save_len;
+ } else
+ copy_from_filestruct(cutbuffer, cutbottom);
+
+ /* Set the current place we want to where the text from the
+ * cutbuffer ends. */
+ openfile->placewewant = xplustabs();
+ }
+
+ /* Set NO_NEWLINES back to what it was before, since we're done
+ * disturbing the text. */
+ if (!old_no_newlines)
+ UNSET(NO_NEWLINES);
+ } else if (!undoing)
+ update_undo(CUT);
+#endif
+ /* Leave the text in the cutbuffer, and mark the file as
+ * modified. */
+ set_modified();
+
+ /* Update the screen. */
+ edit_refresh_needed = TRUE;
+
+#ifdef ENABLE_COLOR
+ reset_multis(openfile->current, FALSE);
+#endif
+
+#ifdef DEBUG
+ dump_filestruct(cutbuffer);
+#endif
+}
+
+/* Move text from the current filestruct into the cutbuffer. */
+void do_cut_text_void(void)
+{
+#ifndef NANO_TINY
+ add_undo(CUT);
+#endif
+ do_cut_text(
+#ifndef NANO_TINY
+ FALSE, FALSE, FALSE
+#endif
+ );
+}
+
+#ifndef NANO_TINY
+/* Move text from the current filestruct into the cutbuffer, and copy it
+ * back into the filestruct afterward. */
+void do_copy_text(void)
+{
+ do_cut_text(TRUE, FALSE, FALSE);
+}
+
+/* Cut from the current cursor position to the end of the file. */
+void do_cut_till_end(void)
+{
+#ifndef NANO_TINY
+ add_undo(CUT);
+#endif
+ do_cut_text(FALSE, TRUE, FALSE);
+}
+#endif /* !NANO_TINY */
+
+/* Copy text from the cutbuffer into the current filestruct. */
+void do_uncut_text(void)
+{
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ /* If the cutbuffer is empty, get out. */
+ if (cutbuffer == NULL)
+ return;
+
+#ifndef NANO_TINY
+ update_undo(UNCUT);
+#endif
+
+ /* Add a copy of the text in the cutbuffer to the current filestruct
+ * at the current cursor position. */
+ copy_from_filestruct(cutbuffer, cutbottom);
+
+ /* Set the current place we want to where the text from the
+ * cutbuffer ends. */
+ openfile->placewewant = xplustabs();
+
+ /* Mark the file as modified. */
+ set_modified();
+
+ /* Update the screen. */
+ edit_refresh_needed = TRUE;
+
+#ifdef ENABLE_COLOR
+ reset_multis(openfile->current, FALSE);
+#endif
+
+#ifdef DEBUG
+ dump_filestruct_reverse();
+#endif
+}
diff --git a/src/files.c b/src/files.c
new file mode 100644
index 0000000..f6efbf1
--- /dev/null
+++ b/src/files.c
@@ -0,0 +1,3041 @@
+/* $Id: files.c 4534 2011-02-24 02:47:25Z astyanax $ */
+/**************************************************************************
+ * files.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <pwd.h>
+
+/* Add an entry to the openfile openfilestruct. This should only be
+ * called from open_buffer(). */
+void make_new_buffer(void)
+{
+ /* If there are no entries in openfile, make the first one and
+ * move to it. */
+ if (openfile == NULL) {
+ openfile = make_new_opennode();
+ splice_opennode(openfile, openfile, openfile);
+ /* Otherwise, make a new entry for openfile, splice it in after
+ * the current entry, and move to it. */
+ } else {
+ splice_opennode(openfile, make_new_opennode(), openfile->next);
+ openfile = openfile->next;
+ }
+
+ /* Initialize the new buffer. */
+ initialize_buffer();
+}
+
+/* Initialize the current entry of the openfile openfilestruct. */
+void initialize_buffer(void)
+{
+ assert(openfile != NULL);
+
+ openfile->filename = mallocstrcpy(NULL, "");
+
+ initialize_buffer_text();
+
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+ openfile->current_y = 0;
+
+ openfile->modified = FALSE;
+#ifndef NANO_TINY
+ openfile->mark_set = FALSE;
+
+ openfile->mark_begin = NULL;
+ openfile->mark_begin_x = 0;
+
+ openfile->fmt = NIX_FILE;
+
+ openfile->current_stat = NULL;
+ openfile->undotop = NULL;
+ openfile->current_undo = NULL;
+#endif
+#ifdef ENABLE_COLOR
+ openfile->colorstrings = NULL;
+#endif
+}
+
+/* Initialize the text of the current entry of the openfile
+ * openfilestruct. */
+void initialize_buffer_text(void)
+{
+ assert(openfile != NULL);
+
+ openfile->fileage = make_new_node(NULL);
+ openfile->fileage->data = mallocstrcpy(NULL, "");
+
+ openfile->filebot = openfile->fileage;
+ openfile->edittop = openfile->fileage;
+ openfile->current = openfile->fileage;
+
+#ifdef ENABLE_COLOR
+ openfile->fileage->multidata = NULL;
+#endif
+
+ openfile->totsize = 0;
+}
+
+/* If it's not "", filename is a file to open. We make a new buffer, if
+ * necessary, and then open and read the file, if applicable. */
+void open_buffer(const char *filename, bool undoable)
+{
+ bool new_buffer = (openfile == NULL
+#ifdef ENABLE_MULTIBUFFER
+ || ISSET(MULTIBUFFER)
+#endif
+ );
+ /* Whether we load into this buffer or a new one. */
+ FILE *f;
+ int rc;
+ /* rc == -2 means that we have a new file. -1 means that the
+ * open() failed. 0 means that the open() succeeded. */
+
+ assert(filename != NULL);
+
+#ifndef DISABLE_OPERATINGDIR
+ if (check_operating_dir(filename, FALSE)) {
+ statusbar(_("Can't insert file from outside of %s"),
+ operating_dir);
+ return;
+ }
+#endif
+
+ /* If the filename isn't blank, open the file. Otherwise, treat it
+ * as a new file. */
+ rc = (filename[0] != '\0') ? open_file(filename, new_buffer, &f) :
+ -2;
+
+ /* If we're loading into a new buffer, add a new entry to
+ * openfile. */
+ if (new_buffer)
+ make_new_buffer();
+
+ /* If we have a file, and we're loading into a new buffer, update
+ * the filename. */
+ if (rc != -1 && new_buffer)
+ openfile->filename = mallocstrcpy(openfile->filename, filename);
+
+ /* If we have a non-new file, read it in. Then, if the buffer has
+ * no stat, update the stat, if applicable. */
+ if (rc > 0) {
+ read_file(f, rc, filename, undoable, new_buffer);
+#ifndef NANO_TINY
+ if (openfile->current_stat == NULL) {
+ openfile->current_stat =
+ (struct stat *)nmalloc(sizeof(struct stat));
+ stat(filename, openfile->current_stat);
+ }
+#endif
+ }
+
+ /* If we have a file, and we're loading into a new buffer, move back
+ * to the beginning of the first line of the buffer. */
+ if (rc != -1 && new_buffer) {
+ openfile->current = openfile->fileage;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+ }
+
+#ifdef ENABLE_COLOR
+ /* If we're loading into a new buffer, update the colors to account
+ * for it, if applicable. */
+ if (new_buffer)
+ color_update();
+#endif
+}
+
+#ifndef DISABLE_SPELLER
+/* If it's not "", filename is a file to open. We blow away the text of
+ * the current buffer, and then open and read the file, if
+ * applicable. Note that we skip the operating directory test when
+ * doing this. */
+void replace_buffer(const char *filename)
+{
+ FILE *f;
+ int rc;
+ /* rc == -2 means that we have a new file. -1 means that the
+ * open() failed. 0 means that the open() succeeded. */
+
+ assert(filename != NULL);
+
+ /* If the filename isn't blank, open the file. Otherwise, treat it
+ * as a new file. */
+ rc = (filename[0] != '\0') ? open_file(filename, TRUE, &f) : -2;
+
+ /* Reinitialize the text of the current buffer. */
+ free_filestruct(openfile->fileage);
+ initialize_buffer_text();
+
+ /* If we have a non-new file, read it in. */
+ if (rc > 0)
+ read_file(f, rc, filename, FALSE, TRUE);
+
+ /* Move back to the beginning of the first line of the buffer. */
+ openfile->current = openfile->fileage;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+}
+#endif /* !DISABLE_SPELLER */
+
+/* Update the screen to account for the current buffer. */
+void display_buffer(void)
+{
+ /* Update the titlebar, since the filename may have changed. */
+ titlebar(NULL);
+
+#ifdef ENABLE_COLOR
+ /* Make sure we're using the buffer's associated colors, if
+ * applicable. */
+ color_init();
+#endif
+
+ /* Update the edit window. */
+ edit_refresh();
+}
+
+#ifdef ENABLE_MULTIBUFFER
+/* Switch to the next file buffer if next_buf is TRUE. Otherwise,
+ * switch to the previous file buffer. */
+void switch_to_prevnext_buffer(bool next_buf)
+{
+ assert(openfile != NULL);
+
+ /* If only one file buffer is open, indicate it on the statusbar and
+ * get out. */
+ if (openfile == openfile->next) {
+ statusbar(_("No more open file buffers"));
+ return;
+ }
+
+ /* Switch to the next or previous file buffer, depending on the
+ * value of next_buf. */
+ openfile = next_buf ? openfile->next : openfile->prev;
+
+#ifdef DEBUG
+ fprintf(stderr, "filename is %s\n", openfile->filename);
+#endif
+
+ /* Update the screen to account for the current buffer. */
+ display_buffer();
+
+ /* Indicate the switch on the statusbar. */
+ statusbar(_("Switched to %s"),
+ ((openfile->filename[0] == '\0') ? _("New Buffer") :
+ openfile->filename));
+
+#ifdef DEBUG
+ dump_filestruct(openfile->current);
+#endif
+}
+
+/* Switch to the previous entry in the openfile filebuffer. */
+void switch_to_prev_buffer_void(void)
+{
+ switch_to_prevnext_buffer(FALSE);
+}
+
+/* Switch to the next entry in the openfile filebuffer. */
+void switch_to_next_buffer_void(void)
+{
+ switch_to_prevnext_buffer(TRUE);
+}
+
+/* Delete an entry from the openfile filebuffer, and switch to the one
+ * after it. Return TRUE on success, or FALSE if there are no more open
+ * file buffers. */
+bool close_buffer(void)
+{
+ assert(openfile != NULL);
+
+ /* If only one file buffer is open, get out. */
+ if (openfile == openfile->next)
+ return FALSE;
+
+#ifndef NANO_TINY
+ update_poshistory(openfile->filename, openfile->current->lineno, xplustabs()+1);
+#endif /* NANO_TINY */
+
+ /* Switch to the next file buffer. */
+ switch_to_next_buffer_void();
+
+ /* Close the file buffer we had open before. */
+ unlink_opennode(openfile->prev);
+
+ display_main_list();
+
+ return TRUE;
+}
+#endif /* ENABLE_MULTIBUFFER */
+
+/* A bit of a copy and paste from open_file(), is_file_writable()
+ * just checks whether the file is appendable as a quick
+ * permissions check, and we tend to err on the side of permissiveness
+ * (reporting TRUE when it might be wrong) to not fluster users
+ * editing on odd filesystems by printing incorrect warnings.
+ */
+int is_file_writable(const char *filename)
+{
+ struct stat fileinfo, fileinfo2;
+ int fd;
+ FILE *f;
+ char *full_filename;
+ bool ans = TRUE;
+
+
+ if (ISSET(VIEW_MODE))
+ return TRUE;
+
+ assert(filename != NULL);
+
+ /* Get the specified file's full path. */
+ full_filename = get_full_path(filename);
+
+ /* Okay, if we can't stat the path due to a component's
+ permissions, just try the relative one */
+ if (full_filename == NULL
+ || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
+ full_filename = mallocstrcpy(NULL, filename);
+
+ if ((fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR |
+ S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1
+ || (f = fdopen(fd, "a")) == NULL)
+ ans = FALSE;
+ else
+ fclose(f);
+ close(fd);
+
+ free(full_filename);
+ return ans;
+}
+
+/* We make a new line of text from buf. buf is length buf_len. If
+ * first_line_ins is TRUE, then we put the new line at the top of the
+ * file. Otherwise, we assume prevnode is the last line of the file,
+ * and put our line after prevnode. */
+filestruct *read_line(char *buf, filestruct *prevnode, bool
+ *first_line_ins, size_t buf_len)
+{
+ filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
+
+ /* Convert nulls to newlines. buf_len is the string's real
+ * length. */
+ unsunder(buf, buf_len);
+
+ assert(openfile->fileage != NULL && strlen(buf) == buf_len);
+
+ fileptr->data = mallocstrcpy(NULL, buf);
+
+#ifndef NANO_TINY
+ /* If it's a DOS file ("\r\n"), and file conversion isn't disabled,
+ * strip the '\r' part from fileptr->data. */
+ if (!ISSET(NO_CONVERT) && buf_len > 0 && buf[buf_len - 1] == '\r')
+ fileptr->data[buf_len - 1] = '\0';
+#endif
+
+#ifdef ENABLE_COLOR
+ fileptr->multidata = NULL;
+#endif
+
+ if (*first_line_ins) {
+ /* Special case: We're inserting with the cursor on the first
+ * line. */
+ fileptr->prev = NULL;
+ fileptr->next = openfile->fileage;
+ fileptr->lineno = 1;
+ if (*first_line_ins) {
+ *first_line_ins = FALSE;
+ /* If we're inserting into the first line of the file, then
+ * we want to make sure that our edit buffer stays on the
+ * first line and that fileage stays up to date. */
+ openfile->edittop = fileptr;
+ } else
+ openfile->filebot = fileptr;
+ openfile->fileage = fileptr;
+ } else {
+ assert(prevnode != NULL);
+
+ fileptr->prev = prevnode;
+ fileptr->next = NULL;
+ fileptr->lineno = prevnode->lineno + 1;
+ prevnode->next = fileptr;
+ }
+
+ return fileptr;
+}
+
+/* Read an open file into the current buffer. f should be set to the
+ * open file, and filename should be set to the name of the file.
+ * undoable means do we want to create undo records to try and undo this.
+ * Will also attempt to check file writability if fd > 0 and checkwritable == TRUE
+ */
+void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable)
+{
+ size_t num_lines = 0;
+ /* The number of lines in the file. */
+ size_t len = 0;
+ /* The length of the current line of the file. */
+ size_t i = 0;
+ /* The position in the current line of the file. */
+ size_t bufx = MAX_BUF_SIZE;
+ /* The size of each chunk of the file that we read. */
+ char input = '\0';
+ /* The current input character. */
+ char *buf;
+ /* The buffer where we store chunks of the file. */
+ filestruct *fileptr = openfile->current;
+ /* The current line of the file. */
+ bool first_line_ins = FALSE;
+ /* Whether we're inserting with the cursor on the first line. */
+ int input_int;
+ /* The current value we read from the file, whether an input
+ * character or EOF. */
+ bool writable = TRUE;
+ /* Is the file writable (if we care) */
+#ifndef NANO_TINY
+ int format = 0;
+ /* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
+#endif
+
+ assert(openfile->fileage != NULL && openfile->current != NULL);
+
+ buf = charalloc(bufx);
+ buf[0] = '\0';
+
+#ifndef NANO_TINY
+ if (undoable)
+ add_undo(INSERT);
+#endif
+
+ if (openfile->current == openfile->fileage)
+ first_line_ins = TRUE;
+ else
+ fileptr = openfile->current->prev;
+
+ /* Read the entire file into the filestruct. */
+ while ((input_int = getc(f)) != EOF) {
+ input = (char)input_int;
+
+ /* If it's a *nix file ("\n") or a DOS file ("\r\n"), and file
+ * conversion isn't disabled, handle it! */
+ if (input == '\n') {
+#ifndef NANO_TINY
+ /* If it's a DOS file or a DOS/Mac file ('\r' before '\n' on
+ * the first line if we think it's a *nix file, or on any
+ * line otherwise), and file conversion isn't disabled,
+ * handle it! */
+ if (!ISSET(NO_CONVERT) && (num_lines == 0 || format != 0) &&
+ i > 0 && buf[i - 1] == '\r') {
+ if (format == 0 || format == 2)
+ format++;
+ }
+#endif
+
+ /* Read in the line properly. */
+ fileptr = read_line(buf, fileptr, &first_line_ins, len);
+
+ /* Reset the line length in preparation for the next
+ * line. */
+ len = 0;
+
+ num_lines++;
+ buf[0] = '\0';
+ i = 0;
+#ifndef NANO_TINY
+ /* If it's a Mac file ('\r' without '\n' on the first line if we
+ * think it's a *nix file, or on any line otherwise), and file
+ * conversion isn't disabled, handle it! */
+ } else if (!ISSET(NO_CONVERT) && (num_lines == 0 ||
+ format != 0) && i > 0 && buf[i - 1] == '\r') {
+ /* If we currently think the file is a *nix file, set format
+ * to Mac. If we currently think the file is a DOS file,
+ * set format to both DOS and Mac. */
+ if (format == 0 || format == 1)
+ format += 2;
+
+ /* Read in the line properly. */
+ fileptr = read_line(buf, fileptr, &first_line_ins, len);
+
+ /* Reset the line length in preparation for the next line.
+ * Since we've already read in the next character, reset it
+ * to 1 instead of 0. */
+ len = 1;
+
+ num_lines++;
+ buf[0] = input;
+ buf[1] = '\0';
+ i = 1;
+#endif
+ } else {
+ /* Calculate the total length of the line. It might have
+ * nulls in it, so we can't just use strlen() here. */
+ len++;
+
+ /* Now we allocate a bigger buffer MAX_BUF_SIZE characters
+ * at a time. If we allocate a lot of space for one line,
+ * we may indeed have to use a buffer this big later on, so
+ * we don't decrease it at all. We do free it at the end,
+ * though. */
+ if (i >= bufx - 1) {
+ bufx += MAX_BUF_SIZE;
+ buf = charealloc(buf, bufx);
+ }
+
+ buf[i] = input;
+ buf[i + 1] = '\0';
+ i++;
+ }
+ }
+
+ /* Perhaps this could use some better handling. */
+ if (ferror(f))
+ nperror(filename);
+ fclose(f);
+ if (fd > 0 && checkwritable) {
+ close(fd);
+ writable = is_file_writable(filename);
+ }
+
+#ifndef NANO_TINY
+ /* If file conversion isn't disabled and the last character in this
+ * file is '\r', read it in properly as a Mac format line. */
+ if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
+ len = 1;
+
+ buf[0] = input;
+ buf[1] = '\0';
+ }
+#endif
+
+ /* Did we not get a newline and still have stuff to do? */
+ if (len > 0) {
+#ifndef NANO_TINY
+ /* If file conversion isn't disabled and the last character in
+ * this file is '\r', set format to Mac if we currently think
+ * the file is a *nix file, or to both DOS and Mac if we
+ * currently think the file is a DOS file. */
+ if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' &&
+ (format == 0 || format == 1))
+ format += 2;
+#endif
+
+ /* Read in the last line properly. */
+ fileptr = read_line(buf, fileptr, &first_line_ins, len);
+ num_lines++;
+ }
+
+ free(buf);
+
+ /* If we didn't get a file and we don't already have one, open a
+ * blank buffer. */
+ if (fileptr == NULL)
+ open_buffer("", FALSE);
+
+ /* Attach the file we got to the filestruct. If we got a file of
+ * zero bytes, don't do anything. */
+ if (num_lines > 0) {
+ /* If the file we got doesn't end in a newline, tack its last
+ * line onto the beginning of the line at current. */
+ if (len > 0) {
+ size_t current_len = strlen(openfile->current->data);
+
+ /* Adjust the current x-coordinate to compensate for the
+ * change in the current line. */
+ if (num_lines == 1)
+ openfile->current_x += len;
+ else
+ openfile->current_x = len;
+
+ /* Tack the text at fileptr onto the beginning of the text
+ * at current. */
+ openfile->current->data =
+ charealloc(openfile->current->data, len +
+ current_len + 1);
+ charmove(openfile->current->data + len,
+ openfile->current->data, current_len + 1);
+ strncpy(openfile->current->data, fileptr->data, len);
+
+ /* Don't destroy fileage, edittop, or filebot! */
+ if (fileptr == openfile->fileage)
+ openfile->fileage = openfile->current;
+ if (fileptr == openfile->edittop)
+ openfile->edittop = openfile->current;
+ if (fileptr == openfile->filebot)
+ openfile->filebot = openfile->current;
+
+ /* Move fileptr back one line and blow away the old fileptr,
+ * since its text has been saved. */
+ fileptr = fileptr->prev;
+ if (fileptr != NULL) {
+ if (fileptr->next != NULL)
+ free(fileptr->next);
+ }
+ }
+
+ /* Attach the line at current after the line at fileptr. */
+ if (fileptr != NULL) {
+ fileptr->next = openfile->current;
+ openfile->current->prev = fileptr;
+ }
+
+ /* Renumber starting with the last line of the file we
+ * inserted. */
+ renumber(openfile->current);
+ }
+
+ openfile->totsize += get_totsize(openfile->fileage,
+ openfile->filebot);
+
+ /* If the NO_NEWLINES flag isn't set, and text has been added to
+ * the magicline (i.e. a file that doesn't end in a newline has been
+ * inserted at the end of the current buffer), add a new magicline,
+ * and move the current line down to it. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') {
+ new_magicline();
+ openfile->current = openfile->filebot;
+ openfile->current_x = 0;
+ }
+
+ /* Set the current place we want to the end of the last line of the
+ * file we inserted. */
+ openfile->placewewant = xplustabs();
+
+#ifndef NANO_TINY
+ if (undoable)
+ update_undo(INSERT);
+
+ if (format == 3) {
+ if (writable)
+ statusbar(
+ P_("Read %lu line (Converted from DOS and Mac format)",
+ "Read %lu lines (Converted from DOS and Mac format)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ else
+ statusbar(
+ P_("Read %lu line (Converted from DOS and Mac format - Warning: No write permission)",
+ "Read %lu lines (Converted from DOS and Mac format - Warning: No write permission)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ } else if (format == 2) {
+ openfile->fmt = MAC_FILE;
+ if (writable)
+ statusbar(P_("Read %lu line (Converted from Mac format)",
+ "Read %lu lines (Converted from Mac format)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ else
+ statusbar(P_("Read %lu line (Converted from Mac format - Warning: No write permission)",
+ "Read %lu lines (Converted from Mac format - Warning: No write permission)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ } else if (format == 1) {
+ openfile->fmt = DOS_FILE;
+ if (writable)
+ statusbar(P_("Read %lu line (Converted from DOS format)",
+ "Read %lu lines (Converted from DOS format)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ else
+ statusbar(P_("Read %lu line (Converted from DOS format - Warning: No write permission)",
+ "Read %lu lines (Converted from DOS format - Warning: No write permission)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ } else
+#endif
+ if (writable)
+ statusbar(P_("Read %lu line", "Read %lu lines",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+ else
+ statusbar(P_("Read %lu line ( Warning: No write permission)",
+ "Read %lu lines (Warning: No write permission)",
+ (unsigned long)num_lines), (unsigned long)num_lines);
+}
+
+/* Open the file (and decide if it exists). If newfie is TRUE, display
+ * "New File" if the file is missing. Otherwise, say "[filename] not
+ * found".
+ *
+ * Return -2 if we say "New File", -1 if the file isn't opened, and the
+ * fd opened otherwise. The file might still have an error while reading
+ * with a 0 return value. *f is set to the opened file. */
+int open_file(const char *filename, bool newfie, FILE **f)
+{
+ struct stat fileinfo, fileinfo2;
+ int fd;
+ char *full_filename;
+
+ assert(filename != NULL && f != NULL);
+
+ /* Get the specified file's full path. */
+ full_filename = get_full_path(filename);
+
+ /* Okay, if we can't stat the path due to a component's
+ permissions, just try the relative one */
+ if (full_filename == NULL
+ || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
+ full_filename = mallocstrcpy(NULL, filename);
+
+ if (stat(full_filename, &fileinfo) == -1) {
+ /* Well, maybe we can open the file even if the OS
+ says its not there */
+ if ((fd = open(filename, O_RDONLY)) != -1) {
+ statusbar(_("Reading File"));
+ free(full_filename);
+ return 0;
+ }
+
+ if (newfie) {
+ statusbar(_("New File"));
+ return -2;
+ }
+ statusbar(_("\"%s\" not found"), filename);
+ beep();
+ return -1;
+ } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
+ S_ISBLK(fileinfo.st_mode)) {
+ /* Don't open directories, character files, or block files.
+ * Sorry, /dev/sndstat! */
+ statusbar(S_ISDIR(fileinfo.st_mode) ?
+ _("\"%s\" is a directory") :
+ _("\"%s\" is a device file"), filename);
+ beep();
+ return -1;
+ } else if ((fd = open(full_filename, O_RDONLY)) == -1) {
+ statusbar(_("Error reading %s: %s"), filename,
+ strerror(errno));
+ beep();
+ return -1;
+ } else {
+ /* The file is A-OK. Open it. */
+ *f = fdopen(fd, "rb");
+
+ if (*f == NULL) {
+ statusbar(_("Error reading %s: %s"), filename,
+ strerror(errno));
+ beep();
+ close(fd);
+ } else
+ statusbar(_("Reading File"));
+ }
+
+ free(full_filename);
+
+ return fd;
+}
+
+/* This function will return the name of the first available extension
+ * of a filename (starting with [name][suffix], then [name][suffix].1,
+ * etc.). Memory is allocated for the return value. If no writable
+ * extension exists, we return "". */
+char *get_next_filename(const char *name, const char *suffix)
+{
+ static int ulmax_digits = -1;
+ unsigned long i = 0;
+ char *buf;
+ size_t namelen, suffixlen;
+
+ assert(name != NULL && suffix != NULL);
+
+ if (ulmax_digits == -1)
+ ulmax_digits = digits(ULONG_MAX);
+
+ namelen = strlen(name);
+ suffixlen = strlen(suffix);
+
+ buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
+ sprintf(buf, "%s%s", name, suffix);
+
+ while (TRUE) {
+ struct stat fs;
+
+ if (stat(buf, &fs) == -1)
+ return buf;
+ if (i == ULONG_MAX)
+ break;
+
+ i++;
+ sprintf(buf + namelen + suffixlen, ".%lu", i);
+ }
+
+ /* We get here only if there is no possible save file. Blank out
+ * the filename to indicate this. */
+ null_at(&buf, 0);
+
+ return buf;
+}
+
+/* Insert a file into a new buffer if the MULTIBUFFER flag is set, or
+ * into the current buffer if it isn't. If execute is TRUE, insert the
+ * output of an executed command instead of a file. */
+void do_insertfile(
+#ifndef NANO_TINY
+ bool execute
+#else
+ void
+#endif
+ )
+{
+ int i;
+ const char *msg;
+ char *ans = mallocstrcpy(NULL, "");
+ /* The last answer the user typed at the statusbar prompt. */
+ filestruct *edittop_save = openfile->edittop;
+ size_t current_x_save = openfile->current_x;
+ ssize_t current_y_save = openfile->current_y;
+ bool edittop_inside = FALSE, meta_key = FALSE, func_key = FALSE;
+ const sc *s;
+#ifndef NANO_TINY
+ bool right_side_up = FALSE, single_line = FALSE;
+#endif
+
+ currmenu = MINSERTFILE;
+
+ while (TRUE) {
+#ifndef NANO_TINY
+ if (execute) {
+ msg =
+#ifdef ENABLE_MULTIBUFFER
+ ISSET(MULTIBUFFER) ?
+ _("Command to execute in new buffer [from %s] ") :
+#endif
+ _("Command to execute [from %s] ");
+ } else {
+#endif
+ msg =
+#ifdef ENABLE_MULTIBUFFER
+ ISSET(MULTIBUFFER) ?
+ _("File to insert into new buffer [from %s] ") :
+#endif
+ _("File to insert [from %s] ");
+#ifndef NANO_TINY
+ }
+#endif
+
+ i = do_prompt(TRUE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+#ifndef NANO_TINY
+ execute ? MEXTCMD :
+#endif
+ MINSERTFILE, ans,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ NULL,
+#endif
+ edit_refresh, msg,
+#ifndef DISABLE_OPERATINGDIR
+ operating_dir != NULL && strcmp(operating_dir,
+ ".") != 0 ? operating_dir :
+#endif
+ "./");
+
+ /* If we're in multibuffer mode and the filename or command is
+ * blank, open a new buffer instead of canceling. If the
+ * filename or command begins with a newline (i.e. an encoded
+ * null), treat it as though it's blank. */
+ if (i == -1 || ((i == -2 || *answer == '\n')
+#ifdef ENABLE_MULTIBUFFER
+ && !ISSET(MULTIBUFFER)
+#endif
+ )) {
+ statusbar(_("Cancelled"));
+ break;
+ } else {
+ size_t pww_save = openfile->placewewant;
+
+ ans = mallocstrcpy(ans, answer);
+
+ s = get_shortcut(currmenu, &i, &meta_key, &func_key);
+
+#ifndef NANO_TINY
+#ifdef ENABLE_MULTIBUFFER
+
+ if (s && s->scfunc == new_buffer_void) {
+ /* Don't allow toggling if we're in view mode. */
+ if (!ISSET(VIEW_MODE))
+ TOGGLE(MULTIBUFFER);
+ continue;
+ } else
+#endif
+ if (s && s->scfunc == ext_cmd_void) {
+ execute = !execute;
+ continue;
+ }
+#ifndef DISABLE_BROWSER
+ else
+#endif
+#endif /* !NANO_TINY */
+
+#ifndef DISABLE_BROWSER
+ if (s && s->scfunc == to_files_void) {
+ char *tmp = do_browse_from(answer);
+
+ if (tmp == NULL)
+ continue;
+
+ /* We have a file now. Indicate this. */
+ free(answer);
+ answer = tmp;
+
+ i = 0;
+ }
+#endif
+
+ /* If we don't have a file yet, go back to the statusbar
+ * prompt. */
+ if (i != 0
+#ifdef ENABLE_MULTIBUFFER
+ && (i != -2 || !ISSET(MULTIBUFFER))
+#endif
+ )
+ continue;
+
+#ifndef NANO_TINY
+ /* Keep track of whether the mark begins inside the
+ * partition and will need adjustment. */
+ if (openfile->mark_set) {
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x,
+ &right_side_up);
+
+ single_line = (top == bot);
+ }
+#endif
+
+#ifdef ENABLE_MULTIBUFFER
+ if (!ISSET(MULTIBUFFER)) {
+#endif
+ /* If we're not inserting into a new buffer, partition
+ * the filestruct so that it contains no text and hence
+ * looks like a new buffer, and keep track of whether
+ * the top of the edit window is inside the
+ * partition. */
+ filepart = partition_filestruct(openfile->current,
+ openfile->current_x, openfile->current,
+ openfile->current_x);
+ edittop_inside =
+ (openfile->edittop == openfile->fileage);
+#ifdef ENABLE_MULTIBUFFER
+ }
+#endif
+
+ /* Convert newlines to nulls, just before we insert the file
+ * or execute the command. */
+ sunder(answer);
+ align(&answer);
+
+#ifndef NANO_TINY
+ if (execute) {
+#ifdef ENABLE_MULTIBUFFER
+ if (ISSET(MULTIBUFFER))
+ /* Open a blank buffer. */
+ open_buffer("", FALSE);
+#endif
+
+ /* Save the command's output in the current buffer. */
+ execute_command(answer);
+
+#ifdef ENABLE_MULTIBUFFER
+ if (ISSET(MULTIBUFFER)) {
+ /* Move back to the beginning of the first line of
+ * the buffer. */
+ openfile->current = openfile->fileage;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+ }
+#endif
+ } else {
+#endif /* !NANO_TINY */
+ /* Make sure the path to the file specified in answer is
+ * tilde-expanded. */
+ answer = mallocstrassn(answer,
+ real_dir_from_tilde(answer));
+
+ /* Save the file specified in answer in the current
+ * buffer. */
+ open_buffer(answer, TRUE);
+#ifndef NANO_TINY
+ }
+#endif
+
+#ifdef ENABLE_MULTIBUFFER
+ if (ISSET(MULTIBUFFER))
+ /* Update the screen to account for the current
+ * buffer. */
+ display_buffer();
+ else
+#endif
+ {
+ filestruct *top_save = openfile->fileage;
+
+ /* If we were at the top of the edit window before, set
+ * the saved value of edittop to the new top of the edit
+ * window. */
+ if (edittop_inside)
+ edittop_save = openfile->fileage;
+
+ /* Update the current x-coordinate to account for the
+ * number of characters inserted on the current line.
+ * If the mark begins inside the partition, adjust the
+ * mark coordinates to compensate for the change in the
+ * current line. */
+ openfile->current_x = strlen(openfile->filebot->data);
+ if (openfile->fileage == openfile->filebot) {
+#ifndef NANO_TINY
+ if (openfile->mark_set) {
+ openfile->mark_begin = openfile->current;
+ if (!right_side_up)
+ openfile->mark_begin_x +=
+ openfile->current_x;
+ }
+#endif
+ openfile->current_x += current_x_save;
+ }
+#ifndef NANO_TINY
+ else if (openfile->mark_set) {
+ if (!right_side_up) {
+ if (single_line) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x -= current_x_save;
+ } else
+ openfile->mark_begin_x -=
+ openfile->current_x;
+ }
+ }
+#endif
+
+ /* Update the current y-coordinate to account for the
+ * number of lines inserted. */
+ openfile->current_y += current_y_save;
+
+ /* Unpartition the filestruct so that it contains all
+ * the text again. Note that we've replaced the
+ * non-text originally in the partition with the text in
+ * the inserted file/executed command output. */
+ unpartition_filestruct(&filepart);
+
+ /* Renumber starting with the beginning line of the old
+ * partition. */
+ renumber(top_save);
+
+ /* Restore the old edittop. */
+ openfile->edittop = edittop_save;
+
+ /* Restore the old place we want. */
+ openfile->placewewant = pww_save;
+
+ /* Mark the file as modified. */
+ set_modified();
+
+ /* Update the screen. */
+ edit_refresh();
+ }
+
+ break;
+ }
+ }
+ shortcut_init(FALSE);
+
+ free(ans);
+}
+
+/* Insert a file into a new buffer or the current buffer, depending on
+ * whether the MULTIBUFFER flag is set. If we're in view mode, only
+ * allow inserting a file into a new buffer. */
+void do_insertfile_void(void)
+{
+
+ if (ISSET(RESTRICTED)) {
+ nano_disabled_msg();
+ return;
+ }
+
+#ifdef ENABLE_MULTIBUFFER
+ if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
+ statusbar(_("Key invalid in non-multibuffer mode"));
+ else
+#endif
+ do_insertfile(
+#ifndef NANO_TINY
+ FALSE
+#endif
+ );
+
+ display_main_list();
+}
+
+/* When passed "[relative path]" or "[relative path][filename]" in
+ * origpath, return "[full path]" or "[full path][filename]" on success,
+ * or NULL on error. Do this if the file doesn't exist but the relative
+ * path does, since the file could exist in memory but not yet on disk).
+ * Don't do this if the relative path doesn't exist, since we won't be
+ * able to go there. */
+char *get_full_path(const char *origpath)
+{
+ struct stat fileinfo;
+ char *d_here, *d_there, *d_there_file = NULL;
+ const char *last_slash;
+ bool path_only;
+
+ if (origpath == NULL)
+ return NULL;
+
+ /* Get the current directory. If it doesn't exist, back up and try
+ * again until we get a directory that does, and use that as the
+ * current directory. */
+ d_here = charalloc(PATH_MAX + 1);
+ d_here = getcwd(d_here, PATH_MAX + 1);
+
+ while (d_here == NULL) {
+ if (chdir("..") == -1)
+ break;
+
+ d_here = getcwd(d_here, PATH_MAX + 1);
+ }
+
+ /* If we succeeded, canonicalize it in d_here. */
+ if (d_here != NULL) {
+ align(&d_here);
+
+ /* If the current directory isn't "/", tack a slash onto the end
+ * of it. */
+ if (strcmp(d_here, "/") != 0) {
+ d_here = charealloc(d_here, strlen(d_here) + 2);
+ strcat(d_here, "/");
+ }
+ /* Otherwise, set d_here to "". */
+ } else
+ d_here = mallocstrcpy(NULL, "");
+
+ d_there = real_dir_from_tilde(origpath);
+
+ /* If stat()ing d_there fails, assume that d_there refers to a new
+ * file that hasn't been saved to disk yet. Set path_only to TRUE
+ * if d_there refers to a directory, and FALSE otherwise. */
+ path_only = (stat(d_there, &fileinfo) != -1 &&
+ S_ISDIR(fileinfo.st_mode));
+
+ /* If path_only is TRUE, make sure d_there ends in a slash. */
+ if (path_only) {
+ size_t d_there_len = strlen(d_there);
+
+ if (d_there[d_there_len - 1] != '/') {
+ d_there = charealloc(d_there, d_there_len + 2);
+ strcat(d_there, "/");
+ }
+ }
+
+ /* Search for the last slash in d_there. */
+ last_slash = strrchr(d_there, '/');
+
+ /* If we didn't find one, then make sure the answer is in the format
+ * "d_here/d_there". */
+ if (last_slash == NULL) {
+ assert(!path_only);
+
+ d_there_file = d_there;
+ d_there = d_here;
+ } else {
+ /* If path_only is FALSE, then save the filename portion of the
+ * answer (everything after the last slash) in d_there_file. */
+ if (!path_only)
+ d_there_file = mallocstrcpy(NULL, last_slash + 1);
+
+ /* Remove the filename portion of the answer from d_there. */
+ null_at(&d_there, last_slash - d_there + 1);
+
+ /* Go to the path specified in d_there. */
+ if (chdir(d_there) == -1) {
+ free(d_there);
+ d_there = NULL;
+ } else {
+ free(d_there);
+
+ /* Get the full path. */
+ d_there = charalloc(PATH_MAX + 1);
+ d_there = getcwd(d_there, PATH_MAX + 1);
+
+ /* If we succeeded, canonicalize it in d_there. */
+ if (d_there != NULL) {
+ align(&d_there);
+
+ /* If the current directory isn't "/", tack a slash onto
+ * the end of it. */
+ if (strcmp(d_there, "/") != 0) {
+ d_there = charealloc(d_there, strlen(d_there) + 2);
+ strcat(d_there, "/");
+ }
+ } else
+ /* Otherwise, set path_only to TRUE, so that we clean up
+ * correctly, free all allocated memory, and return
+ * NULL. */
+ path_only = TRUE;
+
+ /* Finally, go back to the path specified in d_here,
+ * where we were before. We don't check for a chdir()
+ * error, since we can do nothing if we get one. */
+ IGNORE_CALL_RESULT(chdir(d_here));
+
+ /* Free d_here, since we're done using it. */
+ free(d_here);
+ }
+ }
+
+ /* At this point, if path_only is FALSE and d_there isn't NULL,
+ * d_there contains the path portion of the answer and d_there_file
+ * contains the filename portion of the answer. If this is the
+ * case, tack the latter onto the end of the former. d_there will
+ * then contain the complete answer. */
+ if (!path_only && d_there != NULL) {
+ d_there = charealloc(d_there, strlen(d_there) +
+ strlen(d_there_file) + 1);
+ strcat(d_there, d_there_file);
+ }
+
+ /* Free d_there_file, since we're done using it. */
+ if (d_there_file != NULL)
+ free(d_there_file);
+
+ return d_there;
+}
+
+/* Return the full version of path, as returned by get_full_path(). On
+ * error, if path doesn't reference a directory, or if the directory
+ * isn't writable, return NULL. */
+char *check_writable_directory(const char *path)
+{
+ char *full_path = get_full_path(path);
+
+ /* If get_full_path() fails, return NULL. */
+ if (full_path == NULL)
+ return NULL;
+
+ /* If we can't write to path or path isn't a directory, return
+ * NULL. */
+ if (access(full_path, W_OK) != 0 ||
+ full_path[strlen(full_path) - 1] != '/') {
+ free(full_path);
+ return NULL;
+ }
+
+ /* Otherwise, return the full path. */
+ return full_path;
+}
+
+/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
+ * On success, it returns the malloc()ed filename and corresponding FILE
+ * stream, opened in "r+b" mode. On error, it returns NULL for the
+ * filename and leaves the FILE stream unchanged. */
+char *safe_tempfile(FILE **f)
+{
+ char *full_tempdir = NULL;
+ const char *tmpdir_env;
+ int fd;
+ mode_t original_umask = 0;
+
+ assert(f != NULL);
+
+ /* If $TMPDIR is set, set tempdir to it, run it through
+ * get_full_path(), and save the result in full_tempdir. Otherwise,
+ * leave full_tempdir set to NULL. */
+ tmpdir_env = getenv("TMPDIR");
+ if (tmpdir_env != NULL)
+ full_tempdir = check_writable_directory(tmpdir_env);
+
+ /* If $TMPDIR is unset, empty, or not a writable directory, and
+ * full_tempdir is NULL, try P_tmpdir instead. */
+ if (full_tempdir == NULL)
+ full_tempdir = check_writable_directory(P_tmpdir);
+
+ /* if P_tmpdir is NULL, use /tmp. */
+ if (full_tempdir == NULL)
+ full_tempdir = mallocstrcpy(NULL, "/tmp/");
+
+ full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
+ strcat(full_tempdir, "nano.XXXXXX");
+
+ original_umask = umask(0);
+ umask(S_IRWXG | S_IRWXO);
+
+ fd = mkstemp(full_tempdir);
+
+ if (fd != -1)
+ *f = fdopen(fd, "r+b");
+ else {
+ free(full_tempdir);
+ full_tempdir = NULL;
+ }
+
+ umask(original_umask);
+
+ return full_tempdir;
+}
+
+#ifndef DISABLE_OPERATINGDIR
+/* Initialize full_operating_dir based on operating_dir. */
+void init_operating_dir(void)
+{
+ assert(full_operating_dir == NULL);
+
+ if (operating_dir == NULL)
+ return;
+
+ full_operating_dir = get_full_path(operating_dir);
+
+ /* If get_full_path() failed or the operating directory is
+ * inaccessible, unset operating_dir. */
+ if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
+ free(full_operating_dir);
+ full_operating_dir = NULL;
+ free(operating_dir);
+ operating_dir = NULL;
+ }
+}
+
+/* Check to see if we're inside the operating directory. Return FALSE
+ * if we are, or TRUE otherwise. If allow_tabcomp is TRUE, allow
+ * incomplete names that would be matches for the operating directory,
+ * so that tab completion will work. */
+bool check_operating_dir(const char *currpath, bool allow_tabcomp)
+{
+ /* full_operating_dir is global for memory cleanup. It should have
+ * already been initialized by init_operating_dir(). Also, a
+ * relative operating directory path will only be handled properly
+ * if this is done. */
+
+ char *fullpath;
+ bool retval = FALSE;
+ const char *whereami1, *whereami2 = NULL;
+
+ /* If no operating directory is set, don't bother doing anything. */
+ if (operating_dir == NULL)
+ return FALSE;
+
+ assert(full_operating_dir != NULL);
+
+ fullpath = get_full_path(currpath);
+
+ /* If fullpath is NULL, it means some directory in the path doesn't
+ * exist or is unreadable. If allow_tabcomp is FALSE, then currpath
+ * is what the user typed somewhere. We don't want to report a
+ * non-existent directory as being outside the operating directory,
+ * so we return FALSE. If allow_tabcomp is TRUE, then currpath
+ * exists, but is not executable. So we say it isn't in the
+ * operating directory. */
+ if (fullpath == NULL)
+ return allow_tabcomp;
+
+ whereami1 = strstr(fullpath, full_operating_dir);
+ if (allow_tabcomp)
+ whereami2 = strstr(full_operating_dir, fullpath);
+
+ /* If both searches failed, we're outside the operating directory.
+ * Otherwise, check the search results. If the full operating
+ * directory path is not at the beginning of the full current path
+ * (for normal usage) and vice versa (for tab completion, if we're
+ * allowing it), we're outside the operating directory. */
+ if (whereami1 != fullpath && whereami2 != full_operating_dir)
+ retval = TRUE;
+ free(fullpath);
+
+ /* Otherwise, we're still inside it. */
+ return retval;
+}
+#endif
+
+#ifndef NANO_TINY
+/* Although this sucks, it sucks less than having a single 'my system is messed up
+ * and I'm blanket allowing insecure file writing operations.
+ */
+
+int prompt_failed_backupwrite(const char *filename)
+{
+ static int i;
+ static char *prevfile = NULL; /* What was the laast file we were paased so we don't keep asking this?
+ though maybe we should.... */
+ if (prevfile == NULL || strcmp(filename, prevfile)) {
+ i = do_yesno_prompt(FALSE,
+ _("Failed to write backup file, continue saving? (Say N if unsure) "));
+ prevfile = mallocstrcpy(prevfile, filename);
+ }
+ return i;
+}
+
+void init_backup_dir(void)
+{
+ char *full_backup_dir;
+
+ if (backup_dir == NULL)
+ return;
+
+ full_backup_dir = get_full_path(backup_dir);
+
+ /* If get_full_path() failed or the backup directory is
+ * inaccessible, unset backup_dir. */
+ if (full_backup_dir == NULL ||
+ full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
+ free(full_backup_dir);
+ free(backup_dir);
+ backup_dir = NULL;
+ } else {
+ free(backup_dir);
+ backup_dir = full_backup_dir;
+ }
+}
+#endif
+
+/* Read from inn, write to out. We assume inn is opened for reading,
+ * and out for writing. We return 0 on success, -1 on read error, or -2
+ * on write error. */
+int copy_file(FILE *inn, FILE *out)
+{
+ int retval = 0;
+ char buf[BUFSIZ];
+ size_t charsread;
+
+ assert(inn != NULL && out != NULL && inn != out);
+
+ do {
+ charsread = fread(buf, sizeof(char), BUFSIZ, inn);
+ if (charsread == 0 && ferror(inn)) {
+ retval = -1;
+ break;
+ }
+ if (fwrite(buf, sizeof(char), charsread, out) < charsread) {
+ retval = -2;
+ break;
+ }
+ } while (charsread > 0);
+
+ if (fclose(inn) == EOF)
+ retval = -1;
+ if (fclose(out) == EOF)
+ retval = -2;
+
+ return retval;
+}
+
+/* Write a file out to disk. If f_open isn't NULL, we assume that it is
+ * a stream associated with the file, and we don't try to open it
+ * ourselves. If tmp is TRUE, we set the umask to disallow anyone else
+ * from accessing the file, we don't set the filename to its name, and
+ * we don't print out how many lines we wrote on the statusbar.
+ *
+ * tmp means we are writing a temporary file in a secure fashion. We
+ * use it when spell checking or dumping the file on an error. If
+ * append is APPEND, it means we are appending instead of overwriting.
+ * If append is PREPEND, it means we are prepending instead of
+ * overwriting. If nonamechange is TRUE, we don't change the current
+ * filename. nonamechange is ignored if tmp is FALSE, we're appending,
+ * or we're prepending.
+ *
+ * Return TRUE on success or FALSE on error. */
+bool write_file(const char *name, FILE *f_open, bool tmp, append_type
+ append, bool nonamechange)
+{
+ bool retval = FALSE;
+ /* Instead of returning in this function, you should always
+ * set retval and then goto cleanup_and_exit. */
+ size_t lineswritten = 0;
+ const filestruct *fileptr = openfile->fileage;
+ int fd;
+ /* The file descriptor we use. */
+ mode_t original_umask = 0;
+ /* Our umask, from when nano started. */
+ bool realexists;
+ /* The result of stat(). TRUE if the file exists, FALSE
+ * otherwise. If name is a link that points nowhere, realexists
+ * is FALSE. */
+ struct stat st;
+ /* The status fields filled in by stat(). */
+ bool anyexists;
+ /* The result of lstat(). The same as realexists, unless name
+ * is a link. */
+ struct stat lst;
+ /* The status fields filled in by lstat(). */
+ char *realname;
+ /* name after tilde expansion. */
+ FILE *f = NULL;
+ /* The actual file, realname, we are writing to. */
+ char *tempname = NULL;
+ /* The temp file name we write to on prepend. */
+ int backup_cflags;
+
+ assert(name != NULL);
+
+ if (*name == '\0')
+ return -1;
+
+ if (f_open != NULL)
+ f = f_open;
+
+ if (!tmp)
+ titlebar(NULL);
+
+ realname = real_dir_from_tilde(name);
+
+#ifndef DISABLE_OPERATINGDIR
+ /* If we're writing a temporary file, we're probably going outside
+ * the operating directory, so skip the operating directory test. */
+ if (!tmp && check_operating_dir(realname, FALSE)) {
+ statusbar(_("Can't write outside of %s"), operating_dir);
+ goto cleanup_and_exit;
+ }
+#endif
+
+ anyexists = (lstat(realname, &lst) != -1);
+
+ /* If the temp file exists and isn't already open, give up. */
+ if (tmp && anyexists && f_open == NULL)
+ goto cleanup_and_exit;
+
+ /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
+ * append to a symlink. Here we warn about the contradiction. */
+ if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
+ statusbar(
+ _("Cannot prepend or append to a symlink with --nofollow set"));
+ goto cleanup_and_exit;
+ }
+
+ /* Save the state of the file at the end of the symlink (if there is
+ * one). */
+ realexists = (stat(realname, &st) != -1);
+
+#ifndef NANO_TINY
+ /* if we have not stat()d this file before (say, the user just
+ * specified it interactively), stat and save the value
+ * or else we will chase null pointers when we do
+ * modtime checks, preserve file times, etc. during backup */
+ if (openfile->current_stat == NULL && !tmp && realexists)
+ stat(realname, openfile->current_stat);
+
+ /* We backup only if the backup toggle is set, the file isn't
+ * temporary, and the file already exists. Furthermore, if we
+ * aren't appending, prepending, or writing a selection, we backup
+ * only if the file has not been modified by someone else since nano
+ * opened it. */
+ if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
+ OVERWRITE || openfile->mark_set) || (openfile->current_stat &&
+ openfile->current_stat->st_mtime == st.st_mtime))) {
+ int backup_fd;
+ FILE *backup_file;
+ char *backupname;
+ struct utimbuf filetime;
+ int copy_status;
+
+ /* Save the original file's access and modification times. */
+ filetime.actime = openfile->current_stat->st_atime;
+ filetime.modtime = openfile->current_stat->st_mtime;
+
+ if (f_open == NULL) {
+ /* Open the original file to copy to the backup. */
+ f = fopen(realname, "rb");
+
+ if (f == NULL) {
+ statusbar(_("Error reading %s: %s"), realname,
+ strerror(errno));
+ beep();
+ /* If we can't read from the original file, go on, since
+ * only saving the original file is better than saving
+ * nothing. */
+ goto skip_backup;
+ }
+ }
+
+ /* If backup_dir is set, we set backupname to
+ * backup_dir/backupname~[.number], where backupname is the
+ * canonicalized absolute pathname of realname with every '/'
+ * replaced with a '!'. This means that /home/foo/file is
+ * backed up in backup_dir/!home!foo!file~[.number]. */
+ if (backup_dir != NULL) {
+ char *backuptemp = get_full_path(realname);
+
+ if (backuptemp == NULL)
+ /* If get_full_path() failed, we don't have a
+ * canonicalized absolute pathname, so just use the
+ * filename portion of the pathname. We use tail() so
+ * that e.g. ../backupname will be backed up in
+ * backupdir/backupname~ instead of
+ * backupdir/../backupname~. */
+ backuptemp = mallocstrcpy(NULL, tail(realname));
+ else {
+ size_t i = 0;
+
+ for (; backuptemp[i] != '\0'; i++) {
+ if (backuptemp[i] == '/')
+ backuptemp[i] = '!';
+ }
+ }
+
+ backupname = charalloc(strlen(backup_dir) +
+ strlen(backuptemp) + 1);
+ sprintf(backupname, "%s%s", backup_dir, backuptemp);
+ free(backuptemp);
+ backuptemp = get_next_filename(backupname, "~");
+ if (*backuptemp == '\0') {
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ _("Too many backup files?"));
+ free(backuptemp);
+ free(backupname);
+ /* If we can't write to the backup, DONT go on, since
+ whatever caused the backup file to fail (e.g. disk
+ full may well cause the real file write to fail, which
+ means we could lose both the backup and the original! */
+ goto cleanup_and_exit;
+ } else {
+ free(backupname);
+ backupname = backuptemp;
+ }
+ } else {
+ backupname = charalloc(strlen(realname) + 2);
+ sprintf(backupname, "%s~", realname);
+ }
+
+ /* First, unlink any existing backups. Next, open the backup
+ file with O_CREAT and O_EXCL. If it succeeds, we
+ have a file descriptor to a new backup file. */
+ if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
+ if (prompt_failed_backupwrite(backupname))
+ goto skip_backup;
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ strerror(errno));
+ free(backupname);
+ goto cleanup_and_exit;
+ }
+
+ if (ISSET(INSECURE_BACKUP))
+ backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
+ else
+ backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;
+
+ backup_fd = open(backupname, backup_cflags,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ /* Now we've got a safe file stream. If the previous open()
+ call failed, this will return NULL. */
+ backup_file = fdopen(backup_fd, "wb");
+
+ if (backup_fd < 0 || backup_file == NULL) {
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ strerror(errno));
+ free(backupname);
+ goto cleanup_and_exit;
+ }
+
+ /* We shouldn't worry about chown()ing something if we're not
+ root, since it's likely to fail! */
+ if (geteuid() == NANO_ROOT_UID && fchown(backup_fd,
+ openfile->current_stat->st_uid, openfile->current_stat->st_gid) == -1
+ && !ISSET(INSECURE_BACKUP)) {
+ if (prompt_failed_backupwrite(backupname))
+ goto skip_backup;
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ strerror(errno));
+ free(backupname);
+ fclose(backup_file);
+ goto cleanup_and_exit;
+ }
+
+ if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1
+ && !ISSET(INSECURE_BACKUP)) {
+ if (prompt_failed_backupwrite(backupname))
+ goto skip_backup;
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ strerror(errno));
+ free(backupname);
+ fclose(backup_file);
+ /* If we can't write to the backup, DONT go on, since
+ whatever caused the backup file to fail (e.g. disk
+ full may well cause the real file write to fail, which
+ means we could lose both the backup and the original! */
+ goto cleanup_and_exit;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
+#endif
+
+ /* Copy the file. */
+ copy_status = copy_file(f, backup_file);
+
+ if (copy_status != 0) {
+ statusbar(_("Error reading %s: %s"), realname,
+ strerror(errno));
+ beep();
+ goto cleanup_and_exit;
+ }
+
+ /* And set its metadata. */
+ if (utime(backupname, &filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
+ if (prompt_failed_backupwrite(backupname))
+ goto skip_backup;
+ statusbar(_("Error writing backup file %s: %s"), backupname,
+ strerror(errno));
+ /* If we can't write to the backup, DONT go on, since
+ whatever caused the backup file to fail (e.g. disk
+ full may well cause the real file write to fail, which
+ means we could lose both the backup and the original! */
+ goto cleanup_and_exit;
+ }
+
+ free(backupname);
+ }
+
+ skip_backup:
+#endif /* !NANO_TINY */
+
+ /* If NOFOLLOW_SYMLINKS is set and the file is a link, we aren't
+ * doing prepend or append. So we delete the link first, and just
+ * overwrite. */
+ if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
+ unlink(realname) == -1) {
+ statusbar(_("Error writing %s: %s"), realname, strerror(errno));
+ goto cleanup_and_exit;
+ }
+
+ if (f_open == NULL) {
+ original_umask = umask(0);
+
+ /* If we create a temp file, we don't let anyone else access it.
+ * We create a temp file if tmp is TRUE. */
+ if (tmp)
+ umask(S_IRWXG | S_IRWXO);
+ else
+ umask(original_umask);
+ }
+
+ /* If we're prepending, copy the file to a temp file. */
+ if (append == PREPEND) {
+ int fd_source;
+ FILE *f_source = NULL;
+
+ if (f == NULL) {
+ f = fopen(realname, "rb");
+
+ if (f == NULL) {
+ statusbar(_("Error reading %s: %s"), realname,
+ strerror(errno));
+ beep();
+ goto cleanup_and_exit;
+ }
+ }
+
+ tempname = safe_tempfile(&f);
+
+ if (tempname == NULL) {
+ statusbar(_("Error writing temp file: %s"),
+ strerror(errno));
+ goto cleanup_and_exit;
+ }
+
+ if (f_open == NULL) {
+ fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
+
+ if (fd_source != -1) {
+ f_source = fdopen(fd_source, "rb");
+ if (f_source == NULL) {
+ statusbar(_("Error reading %s: %s"), realname,
+ strerror(errno));
+ beep();
+ close(fd_source);
+ fclose(f);
+ unlink(tempname);
+ goto cleanup_and_exit;
+ }
+ }
+ }
+
+ if (copy_file(f_source, f) != 0) {
+ statusbar(_("Error writing %s: %s"), tempname,
+ strerror(errno));
+ unlink(tempname);
+ goto cleanup_and_exit;
+ }
+ }
+
+ if (f_open == NULL) {
+ /* Now open the file in place. Use O_EXCL if tmp is TRUE. This
+ * is copied from joe, because wiggy says so *shrug*. */
+ fd = open(realname, O_WRONLY | O_CREAT | ((append == APPEND) ?
+ O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
+ S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+
+ /* Set the umask back to the user's original value. */
+ umask(original_umask);
+
+ /* If we couldn't open the file, give up. */
+ if (fd == -1) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+
+ /* tempname has been set only if we're prepending. */
+ if (tempname != NULL)
+ unlink(tempname);
+ goto cleanup_and_exit;
+ }
+
+ f = fdopen(fd, (append == APPEND) ? "ab" : "wb");
+
+ if (f == NULL) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ close(fd);
+ goto cleanup_and_exit;
+ }
+ }
+
+ /* There might not be a magicline. There won't be when writing out
+ * a selection. */
+ assert(openfile->fileage != NULL && openfile->filebot != NULL);
+
+ while (fileptr != NULL) {
+ size_t data_len = strlen(fileptr->data), size;
+
+ /* Convert newlines to nulls, just before we write to disk. */
+ sunder(fileptr->data);
+
+ size = fwrite(fileptr->data, sizeof(char), data_len, f);
+
+ /* Convert nulls to newlines. data_len is the string's real
+ * length. */
+ unsunder(fileptr->data, data_len);
+
+ if (size < data_len) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ fclose(f);
+ goto cleanup_and_exit;
+ }
+
+ /* If we're on the last line of the file, don't write a newline
+ * character after it. If the last line of the file is blank,
+ * this means that zero bytes are written, in which case we
+ * don't count the last line in the total lines written. */
+ if (fileptr == openfile->filebot) {
+ if (fileptr->data[0] == '\0')
+ lineswritten--;
+ } else {
+#ifndef NANO_TINY
+ if (openfile->fmt == DOS_FILE || openfile->fmt ==
+ MAC_FILE) {
+ if (putc('\r', f) == EOF) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ fclose(f);
+ goto cleanup_and_exit;
+ }
+ }
+
+ if (openfile->fmt != MAC_FILE) {
+#endif
+ if (putc('\n', f) == EOF) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ fclose(f);
+ goto cleanup_and_exit;
+ }
+#ifndef NANO_TINY
+ }
+#endif
+ }
+
+ fileptr = fileptr->next;
+ lineswritten++;
+ }
+
+ /* If we're prepending, open the temp file, and append it to f. */
+ if (append == PREPEND) {
+ int fd_source;
+ FILE *f_source = NULL;
+
+ fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
+
+ if (fd_source != -1) {
+ f_source = fdopen(fd_source, "rb");
+ if (f_source == NULL)
+ close(fd_source);
+ }
+
+ if (f_source == NULL) {
+ statusbar(_("Error reading %s: %s"), tempname,
+ strerror(errno));
+ beep();
+ fclose(f);
+ goto cleanup_and_exit;
+ }
+
+ if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ goto cleanup_and_exit;
+ }
+ } else if (fclose(f) != 0) {
+ statusbar(_("Error writing %s: %s"), realname,
+ strerror(errno));
+ goto cleanup_and_exit;
+ }
+
+ if (!tmp && append == OVERWRITE) {
+ if (!nonamechange) {
+ openfile->filename = mallocstrcpy(openfile->filename,
+ realname);
+#ifdef ENABLE_COLOR
+ /* We might have changed the filename, so update the colors
+ * to account for it, and then make sure we're using
+ * them. */
+ color_update();
+ color_init();
+
+ /* If color syntaxes are available and turned on, we need to
+ * call edit_refresh(). */
+ if (openfile->colorstrings != NULL &&
+ !ISSET(NO_COLOR_SYNTAX))
+ edit_refresh();
+#endif
+ }
+
+#ifndef NANO_TINY
+ /* Update current_stat to reference the file as it is now. */
+ if (openfile->current_stat == NULL)
+ openfile->current_stat =
+ (struct stat *)nmalloc(sizeof(struct stat));
+ stat(realname, openfile->current_stat);
+#endif
+
+ statusbar(P_("Wrote %lu line", "Wrote %lu lines",
+ (unsigned long)lineswritten),
+ (unsigned long)lineswritten);
+ openfile->modified = FALSE;
+ titlebar(NULL);
+ }
+
+ retval = TRUE;
+
+ cleanup_and_exit:
+ free(realname);
+ if (tempname != NULL)
+ free(tempname);
+
+ return retval;
+}
+
+#ifndef NANO_TINY
+/* Write a marked selection from a file out to disk. Return TRUE on
+ * success or FALSE on error. */
+bool write_marked_file(const char *name, FILE *f_open, bool tmp,
+ append_type append)
+{
+ bool retval;
+ bool old_modified = openfile->modified;
+ /* write_file() unsets the modified flag. */
+ bool added_magicline = FALSE;
+ /* Whether we added a magicline after filebot. */
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+
+ assert(openfile->mark_set);
+
+ /* Partition the filestruct so that it contains only the marked
+ * text. */
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, NULL);
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+
+ /* Handle the magicline if the NO_NEWLINES flag isn't set. If the
+ * line at filebot is blank, treat it as the magicline and hence the
+ * end of the file. Otherwise, add a magicline and treat it as the
+ * end of the file. */
+ if (!ISSET(NO_NEWLINES) &&
+ (added_magicline = (openfile->filebot->data[0] != '\0')))
+ new_magicline();
+
+ retval = write_file(name, f_open, tmp, append, TRUE);
+
+ /* If the NO_NEWLINES flag isn't set, and we added a magicline,
+ * remove it now. */
+ if (!ISSET(NO_NEWLINES) && added_magicline)
+ remove_magicline();
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again. */
+ unpartition_filestruct(&filepart);
+
+ if (old_modified)
+ set_modified();
+
+ return retval;
+}
+#endif /* !NANO_TINY */
+
+/* Write the current file to disk. If the mark is on, write the current
+ * marked selection to disk. If exiting is TRUE, write the file to disk
+ * regardless of whether the mark is on, and without prompting if the
+ * TEMP_FILE flag is set. Return TRUE on success or FALSE on error. */
+bool do_writeout(bool exiting)
+{
+ int i;
+ append_type append = OVERWRITE;
+ char *ans;
+ /* The last answer the user typed at the statusbar prompt. */
+#ifdef NANO_EXTRA
+ static bool did_credits = FALSE;
+#endif
+ bool retval = FALSE, meta_key = FALSE, func_key = FALSE;
+ const sc *s;
+
+ currmenu = MWRITEFILE;
+
+ if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
+ retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
+ FALSE);
+
+ /* Write succeeded. */
+ if (retval)
+ return retval;
+ }
+
+ ans = mallocstrcpy(NULL,
+#ifndef NANO_TINY
+ (!exiting && openfile->mark_set) ? "" :
+#endif
+ openfile->filename);
+
+ while (TRUE) {
+ const char *msg;
+#ifndef NANO_TINY
+ const char *formatstr, *backupstr;
+
+ formatstr = (openfile->fmt == DOS_FILE) ?
+ _(" [DOS Format]") : (openfile->fmt == MAC_FILE) ?
+ _(" [Mac Format]") : "";
+
+ backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
+
+ /* If we're using restricted mode, don't display the "Write
+ * Selection to File" prompt. This function is disabled, since
+ * it allows reading from or writing to files not specified on
+ * the command line. */
+ if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
+ msg = (append == PREPEND) ?
+ _("Prepend Selection to File") : (append == APPEND) ?
+ _("Append Selection to File") :
+ _("Write Selection to File");
+ else
+#endif /* !NANO_TINY */
+ msg = (append == PREPEND) ? _("File Name to Prepend to") :
+ (append == APPEND) ? _("File Name to Append to") :
+ _("File Name to Write");
+
+ /* If we're using restricted mode, the filename isn't blank,
+ * and we're at the "Write File" prompt, disable tab
+ * completion. */
+ i = do_prompt(!ISSET(RESTRICTED) ||
+ openfile->filename[0] == '\0',
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ MWRITEFILE, ans,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ NULL,
+#endif
+ edit_refresh, "%s%s%s", msg,
+#ifndef NANO_TINY
+ formatstr, backupstr
+#else
+ "", ""
+#endif
+ );
+
+ /* If the filename or command begins with a newline (i.e. an
+ * encoded null), treat it as though it's blank. */
+ if (i < 0 || *answer == '\n') {
+ statusbar(_("Cancelled"));
+ retval = FALSE;
+ break;
+ } else {
+ ans = mallocstrcpy(ans, answer);
+ s = get_shortcut(currmenu, &i, &meta_key, &func_key);
+
+#ifndef DISABLE_BROWSER
+ if (s && s->scfunc == to_files_void) {
+ char *tmp = do_browse_from(answer);
+
+ if (tmp == NULL)
+ continue;
+
+ /* We have a file now. Indicate this. */
+ free(answer);
+ answer = tmp;
+ } else
+#endif /* !DISABLE_BROWSER */
+#ifndef NANO_TINY
+ if (s && s->scfunc == dos_format_void) {
+ openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
+ DOS_FILE;
+ continue;
+ } else if (s && s->scfunc == mac_format_void) {
+ openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
+ MAC_FILE;
+ continue;
+ } else if (s && s->scfunc == backup_file_void) {
+ TOGGLE(BACKUP_FILE);
+ continue;
+ } else
+#endif /* !NANO_TINY */
+ if (s && s->scfunc == prepend_void) {
+ append = (append == PREPEND) ? OVERWRITE : PREPEND;
+ continue;
+ } else if (s && s->scfunc == append_void) {
+ append = (append == APPEND) ? OVERWRITE : APPEND;
+ continue;
+ } else if (s && s->scfunc == do_help_void) {
+ continue;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "filename is %s\n", answer);
+#endif
+
+#ifdef NANO_EXTRA
+ /* If the current file has been modified, we've pressed
+ * Ctrl-X at the edit window to exit, we've pressed "y" at
+ * the "Save modified buffer" prompt to save, we've entered
+ * "zzy" as the filename to save under (hence "xyzzy"), and
+ * this is the first time we've done this, show an Easter
+ * egg. Display the credits. */
+ if (!did_credits && exiting && !ISSET(TEMP_FILE) &&
+ strcasecmp(answer, "zzy") == 0) {
+ do_credits();
+ did_credits = TRUE;
+ retval = FALSE;
+ break;
+ }
+#endif
+
+ if (append == OVERWRITE) {
+ size_t answer_len = strlen(answer);
+ bool name_exists, do_warning;
+ char *full_answer, *full_filename;
+ struct stat st;
+
+ /* Convert newlines to nulls, just before we get the
+ * full path. */
+ sunder(answer);
+
+ full_answer = get_full_path(answer);
+ full_filename = get_full_path(openfile->filename);
+ name_exists = (stat((full_answer == NULL) ? answer :
+ full_answer, &st) != -1);
+ if (openfile->filename[0] == '\0')
+ do_warning = name_exists;
+ else
+ do_warning = (strcmp((full_answer == NULL) ?
+ answer : full_answer, (full_filename == NULL) ?
+ openfile->filename : full_filename) != 0);
+
+ /* Convert nulls to newlines. answer_len is the
+ * string's real length. */
+ unsunder(answer, answer_len);
+
+ if (full_filename != NULL)
+ free(full_filename);
+ if (full_answer != NULL)
+ free(full_answer);
+
+ if (do_warning) {
+ /* If we're using restricted mode, we aren't allowed
+ * to overwrite an existing file with the current
+ * file. We also aren't allowed to change the name
+ * of the current file if it has one, because that
+ * would allow reading from or writing to files not
+ * specified on the command line. */
+ if (ISSET(RESTRICTED))
+ continue;
+
+ if (name_exists) {
+ i = do_yesno_prompt(FALSE,
+ _("File exists, OVERWRITE ? "));
+ if (i == 0 || i == -1)
+ continue;
+ } else
+#ifndef NANO_TINY
+ if (exiting || !openfile->mark_set)
+#endif
+ {
+ i = do_yesno_prompt(FALSE,
+ _("Save file under DIFFERENT NAME ? "));
+ if (i == 0 || i == -1)
+ continue;
+ }
+ }
+#ifndef NANO_TINY
+ /* Complain if the file exists, the name hasn't changed, and the
+ stat information we had before does not match what we have now */
+ else if (name_exists && openfile->current_stat && (openfile->current_stat->st_mtime < st.st_mtime ||
+ openfile->current_stat->st_dev != st.st_dev || openfile->current_stat->st_ino != st.st_ino)) {
+ i = do_yesno_prompt(FALSE,
+ _("File was modified since you opened it, continue saving ? "));
+ if (i == 0 || i == -1)
+ continue;
+ }
+#endif
+
+ }
+
+ /* Convert newlines to nulls, just before we save the
+ * file. */
+ sunder(answer);
+ align(&answer);
+
+ /* Here's where we allow the selected text to be written to
+ * a separate file. If we're using restricted mode, this
+ * function is disabled, since it allows reading from or
+ * writing to files not specified on the command line. */
+ retval =
+#ifndef NANO_TINY
+ (!ISSET(RESTRICTED) && !exiting && openfile->mark_set) ?
+ write_marked_file(answer, NULL, FALSE, append) :
+#endif
+ write_file(answer, NULL, FALSE, append, FALSE);
+
+ break;
+ }
+ }
+
+ free(ans);
+
+ return retval;
+}
+
+/* Write the current file to disk. If the mark is on, write the current
+ * marked selection to disk. */
+void do_writeout_void(void)
+{
+ do_writeout(FALSE);
+ display_main_list();
+}
+
+/* Return a malloc()ed string containing the actual directory, used to
+ * convert ~user/ and ~/ notation. */
+char *real_dir_from_tilde(const char *buf)
+{
+ char *retval;
+
+ assert(buf != NULL);
+
+ if (*buf == '~') {
+ size_t i = 1;
+ char *tilde_dir;
+
+ /* Figure out how much of the string we need to compare. */
+ for (; buf[i] != '/' && buf[i] != '\0'; i++)
+ ;
+
+ /* Get the home directory. */
+ if (i == 1) {
+ get_homedir();
+ tilde_dir = mallocstrcpy(NULL, homedir);
+ } else {
+ const struct passwd *userdata;
+
+ tilde_dir = mallocstrncpy(NULL, buf, i + 1);
+ tilde_dir[i] = '\0';
+
+ do {
+ userdata = getpwent();
+ } while (userdata != NULL && strcmp(userdata->pw_name,
+ tilde_dir + 1) != 0);
+ endpwent();
+ if (userdata != NULL)
+ tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
+ }
+
+ retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
+ sprintf(retval, "%s%s", tilde_dir, buf + i);
+
+ free(tilde_dir);
+ } else
+ retval = mallocstrcpy(NULL, buf);
+
+ return retval;
+}
+
+#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
+/* Our sort routine for file listings. Sort alphabetically and
+ * case-insensitively, and sort directories before filenames. */
+int diralphasort(const void *va, const void *vb)
+{
+ struct stat fileinfo;
+ const char *a = *(const char *const *)va;
+ const char *b = *(const char *const *)vb;
+ bool aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
+ bool bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
+
+ if (aisdir && !bisdir)
+ return -1;
+ if (!aisdir && bisdir)
+ return 1;
+
+ /* Standard function brain damage: We should be sorting
+ * alphabetically and case-insensitively according to the current
+ * locale, but there's no standard strcasecoll() function, so we
+ * have to use multibyte strcasecmp() instead, */
+ return mbstrcasecmp(a, b);
+}
+
+/* Free the memory allocated for array, which should contain len
+ * elements. */
+void free_chararray(char **array, size_t len)
+{
+ assert(array != NULL);
+
+ for (; len > 0; len--)
+ free(array[len - 1]);
+ free(array);
+}
+#endif
+
+#ifndef DISABLE_TABCOMP
+/* Is the given path a directory? */
+bool is_dir(const char *buf)
+{
+ char *dirptr;
+ struct stat fileinfo;
+ bool retval;
+
+ assert(buf != NULL);
+
+ dirptr = real_dir_from_tilde(buf);
+
+ retval = (stat(dirptr, &fileinfo) != -1 &&
+ S_ISDIR(fileinfo.st_mode));
+
+ free(dirptr);
+
+ return retval;
+}
+
+/* These functions, username_tab_completion(), cwd_tab_completion()
+ * (originally exe_n_cwd_tab_completion()), and input_tab(), were
+ * adapted from busybox 0.46 (cmdedit.c). Here is the notice from that
+ * file, with the copyright years updated:
+ *
+ * Termios command line History and Editting, originally
+ * intended for NetBSD sh (ash)
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
+ * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
+ * Etc: Dave Cinege <dcinege@psychosis.com>
+ * Majorly adjusted/re-written for busybox:
+ * Erik Andersen <andersee@debian.org>
+ *
+ * You may use this code as you wish, so long as the original author(s)
+ * are attributed in any redistributions of the source code.
+ * This code is 'as is' with no warranty.
+ * This code may safely be consumed by a BSD or GPL license. */
+
+/* We consider the first buf_len characters of buf for ~username tab
+ * completion. */
+char **username_tab_completion(const char *buf, size_t *num_matches,
+ size_t buf_len)
+{
+ char **matches = NULL;
+ const struct passwd *userdata;
+
+ assert(buf != NULL && num_matches != NULL && buf_len > 0);
+
+ *num_matches = 0;
+
+ while ((userdata = getpwent()) != NULL) {
+ if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
+ /* Cool, found a match. Add it to the list. This makes a
+ * lot more sense to me (Chris) this way... */
+
+#ifndef DISABLE_OPERATINGDIR
+ /* ...unless the match exists outside the operating
+ * directory, in which case just go to the next match. */
+ if (check_operating_dir(userdata->pw_dir, TRUE))
+ continue;
+#endif
+
+ matches = (char **)nrealloc(matches, (*num_matches + 1) *
+ sizeof(char *));
+ matches[*num_matches] =
+ charalloc(strlen(userdata->pw_name) + 2);
+ sprintf(matches[*num_matches], "~%s", userdata->pw_name);
+ ++(*num_matches);
+ }
+ }
+ endpwent();
+
+ return matches;
+}
+
+/* We consider the first buf_len characters of buf for filename tab
+ * completion. */
+char **cwd_tab_completion(const char *buf, bool allow_files, size_t
+ *num_matches, size_t buf_len)
+{
+ char *dirname = mallocstrcpy(NULL, buf), *filename;
+#ifndef DISABLE_OPERATINGDIR
+ size_t dirnamelen;
+#endif
+ size_t filenamelen;
+ char **matches = NULL;
+ DIR *dir;
+ const struct dirent *nextdir;
+
+ assert(dirname != NULL && num_matches != NULL);
+
+ *num_matches = 0;
+ null_at(&dirname, buf_len);
+
+ /* Okie, if there's a / in the buffer, strip out the directory
+ * part. */
+ filename = strrchr(dirname, '/');
+ if (filename != NULL) {
+ char *tmpdirname = filename + 1;
+
+ filename = mallocstrcpy(NULL, tmpdirname);
+ *tmpdirname = '\0';
+ tmpdirname = dirname;
+ dirname = real_dir_from_tilde(dirname);
+ free(tmpdirname);
+ } else {
+ filename = dirname;
+ dirname = mallocstrcpy(NULL, "./");
+ }
+
+ assert(dirname[strlen(dirname) - 1] == '/');
+
+ dir = opendir(dirname);
+
+ if (dir == NULL) {
+ /* Don't print an error, just shut up and return. */
+ beep();
+ free(filename);
+ free(dirname);
+ return NULL;
+ }
+
+#ifndef DISABLE_OPERATINGDIR
+ dirnamelen = strlen(dirname);
+#endif
+ filenamelen = strlen(filename);
+
+ while ((nextdir = readdir(dir)) != NULL) {
+ bool skip_match = FALSE;
+
+#ifdef DEBUG
+ fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
+#endif
+ /* See if this matches. */
+ if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
+ (*filename == '.' || (strcmp(nextdir->d_name, ".") !=
+ 0 && strcmp(nextdir->d_name, "..") != 0))) {
+ /* Cool, found a match. Add it to the list. This makes a
+ * lot more sense to me (Chris) this way... */
+
+ char *tmp = charalloc(strlen(dirname) +
+ strlen(nextdir->d_name) + 1);
+ sprintf(tmp, "%s%s", dirname, nextdir->d_name);
+
+#ifndef DISABLE_OPERATINGDIR
+ /* ...unless the match exists outside the operating
+ * directory, in which case just go to the next match. */
+ if (check_operating_dir(tmp, TRUE))
+ skip_match = TRUE;
+#endif
+
+ /* ...or unless the match isn't a directory and allow_files
+ * isn't set, in which case just go to the next match. */
+ if (!allow_files && !is_dir(tmp))
+ skip_match = TRUE;
+
+ free(tmp);
+
+ if (skip_match)
+ continue;
+
+ matches = (char **)nrealloc(matches, (*num_matches + 1) *
+ sizeof(char *));
+ matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
+ ++(*num_matches);
+ }
+ }
+
+ closedir(dir);
+ free(dirname);
+ free(filename);
+
+ return matches;
+}
+
+/* Do tab completion. place refers to how much the statusbar cursor
+ * position should be advanced. refresh_func is the function we will
+ * call to refresh the edit window. */
+char *input_tab(char *buf, bool allow_files, size_t *place, bool
+ *lastwastab, void (*refresh_func)(void), bool *list)
+{
+ size_t num_matches = 0, buf_len;
+ char **matches = NULL;
+
+ assert(buf != NULL && place != NULL && *place <= strlen(buf) && lastwastab != NULL && refresh_func != NULL && list != NULL);
+
+ *list = FALSE;
+
+ /* If the word starts with `~' and there is no slash in the word,
+ * then try completing this word as a username. */
+ if (*place > 0 && *buf == '~') {
+ const char *bob = strchr(buf, '/');
+
+ if (bob == NULL || bob >= buf + *place)
+ matches = username_tab_completion(buf, &num_matches,
+ *place);
+ }
+
+ /* Match against files relative to the current working directory. */
+ if (matches == NULL)
+ matches = cwd_tab_completion(buf, allow_files, &num_matches,
+ *place);
+
+ buf_len = strlen(buf);
+
+ if (num_matches == 0 || *place != buf_len)
+ beep();
+ else {
+ size_t match, common_len = 0;
+ char *mzero;
+ const char *lastslash = revstrstr(buf, "/", buf + *place);
+ size_t lastslash_len = (lastslash == NULL) ? 0 :
+ lastslash - buf + 1;
+ char *match1_mb = charalloc(mb_cur_max() + 1);
+ char *match2_mb = charalloc(mb_cur_max() + 1);
+ int match1_mb_len, match2_mb_len;
+
+ while (TRUE) {
+ for (match = 1; match < num_matches; match++) {
+ /* Get the number of single-byte characters that all the
+ * matches have in common. */
+ match1_mb_len = parse_mbchar(matches[0] + common_len,
+ match1_mb, NULL);
+ match2_mb_len = parse_mbchar(matches[match] +
+ common_len, match2_mb, NULL);
+ match1_mb[match1_mb_len] = '\0';
+ match2_mb[match2_mb_len] = '\0';
+ if (strcmp(match1_mb, match2_mb) != 0)
+ break;
+ }
+
+ if (match < num_matches || matches[0][common_len] == '\0')
+ break;
+
+ common_len += parse_mbchar(buf + common_len, NULL, NULL);
+ }
+
+ free(match1_mb);
+ free(match2_mb);
+
+ mzero = charalloc(lastslash_len + common_len + 1);
+
+ strncpy(mzero, buf, lastslash_len);
+ strncpy(mzero + lastslash_len, matches[0], common_len);
+
+ common_len += lastslash_len;
+ mzero[common_len] = '\0';
+
+ assert(common_len >= *place);
+
+ if (num_matches == 1 && is_dir(mzero)) {
+ mzero[common_len++] = '/';
+
+ assert(common_len > *place);
+ }
+
+ if (num_matches > 1 && (common_len != *place || !*lastwastab))
+ beep();
+
+ /* If there is more of a match to display on the statusbar, show
+ * it. We reset lastwastab to FALSE: it requires pressing Tab
+ * twice in succession with no statusbar changes to see a match
+ * list. */
+ if (common_len != *place) {
+ *lastwastab = FALSE;
+ buf = charealloc(buf, common_len + buf_len - *place + 1);
+ charmove(buf + common_len, buf + *place, buf_len -
+ *place + 1);
+ strncpy(buf, mzero, common_len);
+ *place = common_len;
+ } else if (!*lastwastab || num_matches < 2)
+ *lastwastab = TRUE;
+ else {
+ int longest_name = 0, ncols, editline = 0;
+
+ /* Now we show a list of the available choices. */
+ assert(num_matches > 1);
+
+ /* Sort the list. */
+ qsort(matches, num_matches, sizeof(char *), diralphasort);
+
+ for (match = 0; match < num_matches; match++) {
+ common_len = strnlenpt(matches[match], COLS - 1);
+
+ if (common_len > COLS - 1) {
+ longest_name = COLS - 1;
+ break;
+ }
+
+ if (common_len > longest_name)
+ longest_name = common_len;
+ }
+
+ assert(longest_name <= COLS - 1);
+
+ /* Each column will be (longest_name + 2) columns wide, i.e.
+ * two spaces between columns, except that there will be
+ * only one space after the last column. */
+ ncols = (COLS + 1) / (longest_name + 2);
+
+ /* Blank the edit window, and print the matches out
+ * there. */
+ blank_edit();
+ wmove(edit, 0, 0);
+
+ /* Disable el cursor. */
+ curs_set(0);
+
+ for (match = 0; match < num_matches; match++) {
+ char *disp;
+
+ wmove(edit, editline, (longest_name + 2) *
+ (match % ncols));
+
+ if (match % ncols == 0 &&
+ editline == editwinrows - 1 &&
+ num_matches - match > ncols) {
+ waddstr(edit, _("(more)"));
+ break;
+ }
+
+ disp = display_string(matches[match], 0, longest_name,
+ FALSE);
+ waddstr(edit, disp);
+ free(disp);
+
+ if ((match + 1) % ncols == 0)
+ editline++;
+ }
+
+ wnoutrefresh(edit);
+ *list = TRUE;
+ }
+
+ free(mzero);
+ }
+
+ free_chararray(matches, num_matches);
+
+ /* Only refresh the edit window if we don't have a list of filename
+ * matches on it. */
+ if (!*list)
+ refresh_func();
+
+ /* Enable el cursor. */
+ curs_set(1);
+
+ return buf;
+}
+#endif /* !DISABLE_TABCOMP */
+
+/* Only print the last part of a path. Isn't there a shell command for
+ * this? */
+const char *tail(const char *foo)
+{
+ const char *tmp = strrchr(foo, '/');
+
+ if (tmp == NULL)
+ tmp = foo;
+ else
+ tmp++;
+
+ return tmp;
+}
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+/* Return the constructed dorfile path, or NULL if we can't find the home
+ * directory. The string is dynamically allocated, and should be
+ * freed. */
+char *construct_filename(const char *str)
+{
+ char *newstr = NULL;
+
+ if (homedir != NULL) {
+ size_t homelen = strlen(homedir);
+
+ newstr = charalloc(homelen + strlen(str) + 1);
+ strcpy(newstr, homedir);
+ strcpy(newstr + homelen, str);
+ }
+
+ return newstr;
+
+}
+
+char *histfilename(void)
+{
+ return construct_filename("/.nano/search_history");
+}
+
+/* Construct the legacy history filename
+ * (Deprecate in 2.5, delete later
+ */
+char *legacyhistfilename(void)
+{
+ return construct_filename("/.nano_history");
+}
+
+char *poshistfilename(void)
+{
+ return construct_filename("/.nano/filepos_history");
+}
+
+
+
+void history_error(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ vfprintf(stderr, _(msg), ap);
+ va_end(ap);
+
+ fprintf(stderr, _("\nPress Enter to continue\n"));
+ while (getchar() != '\n')
+ ;
+
+}
+
+/* Now that we have more than one history file, let's just rely
+ on a .nano dir for this stuff. Return 1 if the dir exists
+ or was successfully created, and return 0 otherwise.
+ */
+int check_dotnano(void)
+{
+ struct stat dirstat;
+ char *nanodir = construct_filename("/.nano");
+
+ if (stat(nanodir, &dirstat) == -1) {
+ if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
+ history_error(N_("Unable to create directory %s: %s\nIt is required for saving/loading search history or cursor position\n"),
+ nanodir, strerror(errno));
+ return 0;
+ }
+ } else if (!S_ISDIR(dirstat.st_mode)) {
+ history_error(N_("Path %s is not a directory and needs to be.\nNano will be unable to load or save search or cursor position history\n"));
+ return 0;
+ }
+ return 1;
+}
+
+/* Load histories from ~/.nano_history. */
+void load_history(void)
+{
+ char *nanohist = histfilename();
+ char *legacyhist = legacyhistfilename();
+ struct stat hstat;
+
+
+ if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
+ if (rename(legacyhist, nanohist) == -1)
+ history_error(N_("Detected a legacy nano history file (%s) which I tried to move\nto the preferred location (%s) but encountered an error: %s"),
+ legacyhist, nanohist, strerror(errno));
+ else
+ history_error(N_("Detected a legacy nano history file (%s) which I moved\nto the preferred location (%s)\n(see the nano FAQ about this change)"),
+ legacyhist, nanohist);
+ }
+
+
+
+ /* Assume do_rcfile() has reported a missing home directory. */
+ if (nanohist != NULL) {
+ FILE *hist = fopen(nanohist, "rb");
+
+ if (hist == NULL) {
+ if (errno != ENOENT) {
+ /* Don't save history when we quit. */
+ UNSET(HISTORYLOG);
+ history_error(N_("Error reading %s: %s"), nanohist,
+ strerror(errno));
+ }
+ } else {
+ /* Load a history list (first the search history, then the
+ * replace history) from the oldest entry to the newest.
+ * Assume the last history entry is a blank line. */
+ filestruct **history = &search_history;
+ char *line = NULL;
+ size_t buf_len = 0;
+ ssize_t read;
+
+ while ((read = getline(&line, &buf_len, hist)) >= 0) {
+ if (read > 0 && line[read - 1] == '\n') {
+ read--;
+ line[read] = '\0';
+ }
+ if (read > 0) {
+ unsunder(line, read);
+ update_history(history, line);
+ } else
+ history = &replace_history;
+ }
+
+ fclose(hist);
+ free(line);
+ if (search_history->prev != NULL)
+ last_search = mallocstrcpy(NULL, search_history->prev->data);
+ }
+ free(nanohist);
+ free(legacyhist);
+ }
+}
+
+/* Write the lines of a history list, starting with the line at h, to
+ * the open file at hist. Return TRUE if the write succeeded, and FALSE
+ * otherwise. */
+bool writehist(FILE *hist, filestruct *h)
+{
+ filestruct *p;
+
+ /* Write a history list from the oldest entry to the newest. Assume
+ * the last history entry is a blank line. */
+ for (p = h; p != NULL; p = p->next) {
+ size_t p_len = strlen(p->data);
+
+ sunder(p->data);
+
+ if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
+ putc('\n', hist) == EOF)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Save histories to ~/.nano/search_history. */
+void save_history(void)
+{
+ char *nanohist;
+
+ /* Don't save unchanged or empty histories. */
+ if (!history_has_changed() || (searchbot->lineno == 1 &&
+ replacebot->lineno == 1))
+ return;
+
+ nanohist = histfilename();
+
+ if (nanohist != NULL) {
+ FILE *hist = fopen(nanohist, "wb");
+
+ if (hist == NULL)
+ history_error(N_("Error writing %s: %s"), nanohist,
+ strerror(errno));
+ else {
+ /* Make sure no one else can read from or write to the
+ * history file. */
+ chmod(nanohist, S_IRUSR | S_IWUSR);
+
+ if (!writehist(hist, searchage) || !writehist(hist,
+ replaceage))
+ history_error(N_("Error writing %s: %s"), nanohist,
+ strerror(errno));
+
+ fclose(hist);
+ }
+
+ free(nanohist);
+ }
+}
+
+
+/* Analogs for the POS history */
+void save_poshistory(void)
+{
+ char *poshist;
+ char *statusstr = NULL;
+ poshiststruct *posptr;
+
+ poshist = poshistfilename();
+
+ if (poshist != NULL) {
+ FILE *hist = fopen(poshist, "wb");
+
+ if (hist == NULL)
+ history_error(N_("Error writing %s: %s"), poshist,
+ strerror(errno));
+ else {
+ /* Make sure no one else can read from or write to the
+ * history file. */
+ chmod(poshist, S_IRUSR | S_IWUSR);
+
+ for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
+ statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
+ sprintf(statusstr, "%s %d %d\n", posptr->filename, (int) posptr->lineno,
+ (int) posptr->xno);
+ if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
+ history_error(N_("Error writing %s: %s"), poshist,
+ strerror(errno));
+ free(statusstr);
+ }
+ fclose(hist);
+ }
+ free(poshist);
+ }
+}
+
+/* Update the POS history, given a filename line and column.
+ * If no entry is found, add a new entry on the end
+ */
+void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
+{
+ poshiststruct *posptr, *posprev = NULL;
+ char *fullpath = get_full_path(filename);
+
+ if (fullpath == NULL)
+ return;
+
+ for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
+ if (!strcmp(posptr->filename, fullpath)) {
+ posptr->lineno = lineno;
+ posptr->xno = xpos;
+ return;
+ }
+ posprev = posptr;
+ }
+
+ /* Didn't find it, make a new node yo! */
+
+ posptr = (poshiststruct *) nmalloc(sizeof(poshiststruct));
+ posptr->filename = mallocstrcpy(NULL, fullpath);
+ posptr->lineno = lineno;
+ posptr->xno = xpos;
+ posptr->next = NULL;
+
+ if (!poshistory)
+ poshistory = posptr;
+ else
+ posprev->next = posptr;
+
+ free(fullpath);
+}
+
+
+/* Check the POS history to see if file matches
+ * an existing entry. If so return 1 and set line and column
+ * to the right values Otherwise return 0
+ */
+int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
+{
+ poshiststruct *posptr;
+ char *fullpath = get_full_path(file);
+
+ if (fullpath == NULL)
+ return 0;
+
+ for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
+ if (!strcmp(posptr->filename, fullpath)) {
+ *line = posptr->lineno;
+ *column = posptr->xno;
+ free(fullpath);
+ return 1;
+ }
+ }
+ free(fullpath);
+ return 0;
+}
+
+
+/* Load histories from ~/.nano_history. */
+void load_poshistory(void)
+{
+ char *nanohist = poshistfilename();
+
+ /* Assume do_rcfile() has reported a missing home directory. */
+ if (nanohist != NULL) {
+ FILE *hist = fopen(nanohist, "rb");
+
+ if (hist == NULL) {
+ if (errno != ENOENT) {
+ /* Don't save history when we quit. */
+ UNSET(HISTORYLOG);
+ history_error(N_("Error reading %s: %s"), nanohist,
+ strerror(errno));
+ }
+ } else {
+ char *line = NULL, *lineptr, *xptr;
+ size_t buf_len = 0;
+ ssize_t read, lineno, xno;
+ poshiststruct *posptr;
+
+ /* See if we can find the file we're currently editing */
+ while ((read = getline(&line, &buf_len, hist)) >= 0) {
+ if (read > 0 && line[read - 1] == '\n') {
+ read--;
+ line[read] = '\0';
+ }
+ if (read > 0) {
+ unsunder(line, read);
+ }
+ lineptr = parse_next_word(line);
+ xptr = parse_next_word(lineptr);
+ lineno = atoi(lineptr);
+ xno = atoi(xptr);
+ if (poshistory == NULL) {
+ poshistory = (poshiststruct *) nmalloc(sizeof(poshiststruct));
+ poshistory->filename = mallocstrcpy(NULL, line);
+ poshistory->lineno = lineno;
+ poshistory->xno = xno;
+ poshistory->next = NULL;
+ } else {
+ for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next)
+ ;
+ posptr->next = (poshiststruct *) nmalloc(sizeof(poshiststruct));
+ posptr->next->filename = mallocstrcpy(NULL, line);
+ posptr->next->lineno = lineno;
+ posptr->next->xno = xno;
+ posptr->next->next = NULL;
+ }
+
+ }
+
+ fclose(hist);
+ free(line);
+ }
+ free(nanohist);
+ }
+}
+
+#endif /* !NANO_TINY && ENABLE_NANORC */
diff --git a/src/global.c b/src/global.c
new file mode 100644
index 0000000..b37563b
--- /dev/null
+++ b/src/global.c
@@ -0,0 +1,1730 @@
+/* $Id: global.c 4535 2011-02-26 14:22:37Z astyanax $ */
+/**************************************************************************
+ * global.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include "assert.h"
+
+/* Global variables. */
+#ifndef NANO_TINY
+sigjmp_buf jump_buf;
+ /* Used to return to either main() or the unjustify routine in
+ * do_justify() after a SIGWINCH. */
+bool jump_buf_main = FALSE;
+ /* Have we set jump_buf so that we return to main() after a
+ * SIGWINCH? */
+#endif
+
+#ifndef DISABLE_WRAPJUSTIFY
+ssize_t fill = 0;
+ /* The column where we will wrap lines. */
+ssize_t wrap_at = -CHARS_FROM_EOL;
+ /* The position where we will wrap lines. fill is equal to this
+ * if it's greater than zero, and equal to (COLS + this) if it
+ * isn't. */
+#endif
+
+char *last_search = NULL;
+ /* The last string we searched for. */
+char *last_replace = NULL;
+ /* The last replacement string we searched for. */
+
+unsigned flags[4] = {0, 0, 0, 0};
+ /* Our flag containing the states of all global options. */
+WINDOW *topwin;
+ /* The top portion of the window, where we display the version
+ * number of nano, the name of the current file, and whether the
+ * current file has been modified. */
+WINDOW *edit;
+ /* The middle portion of the window, i.e. the edit window, where
+ * we display the current file we're editing. */
+WINDOW *bottomwin;
+ /* The bottom portion of the window, where we display statusbar
+ * messages, the statusbar prompt, and a list of shortcuts. */
+int editwinrows = 0;
+ /* How many rows does the edit window take up? */
+int maxrows = 0;
+ /* How many usable lines are there (due to soft wrapping) */
+
+filestruct *cutbuffer = NULL;
+ /* The buffer where we store cut text. */
+filestruct *cutbottom = NULL;
+#ifndef DISABLE_JUSTIFY
+filestruct *jusbuffer = NULL;
+ /* The buffer where we store unjustified text. */
+#endif
+partition *filepart = NULL;
+ /* The partition where we store a portion of the current
+ * file. */
+openfilestruct *openfile = NULL;
+ /* The list of all open file buffers. */
+
+#ifndef NANO_TINY
+char *matchbrackets = NULL;
+ /* The opening and closing brackets that can be found by bracket
+ * searches. */
+#endif
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+char *whitespace = NULL;
+ /* The characters used when displaying the first characters of
+ * tabs and spaces. */
+int whitespace_len[2];
+ /* The length of these characters. */
+#endif
+
+#ifndef DISABLE_JUSTIFY
+char *punct = NULL;
+ /* The closing punctuation that can end sentences. */
+char *brackets = NULL;
+ /* The closing brackets that can follow closing punctuation and
+ * can end sentences. */
+char *quotestr = NULL;
+ /* The quoting string. The default value is set in main(). */
+#ifdef HAVE_REGEX_H
+regex_t quotereg;
+ /* The compiled regular expression from the quoting string. */
+int quoterc;
+ /* Whether it was compiled successfully. */
+char *quoteerr = NULL;
+ /* The error message, if it didn't. */
+#else
+size_t quotelen;
+ /* The length of the quoting string in bytes. */
+#endif
+#endif
+
+bool nodelay_mode = FALSE;
+ /* Are we in nodelay mode (checking for a cancel wile doing something */
+
+char *answer = NULL;
+ /* The answer string used by the statusbar prompt. */
+
+ssize_t tabsize = -1;
+ /* The width of a tab in spaces. The default value is set in
+ * main(). */
+
+#ifndef NANO_TINY
+char *backup_dir = NULL;
+ /* The directory where we store backup files. */
+#endif
+#ifndef DISABLE_OPERATINGDIR
+char *operating_dir = NULL;
+ /* The relative path to the operating directory, which we can't
+ * move outside of. */
+char *full_operating_dir = NULL;
+ /* The full path to it. */
+#endif
+
+#ifndef DISABLE_SPELLER
+char *alt_speller = NULL;
+ /* The command to use for the alternate spell checker. */
+#endif
+
+#ifdef ENABLE_COLOR
+syntaxtype *syntaxes = NULL;
+ /* The global list of color syntaxes. */
+char *syntaxstr = NULL;
+ /* The color syntax name specified on the command line. */
+
+#endif
+
+bool edit_refresh_needed = 0;
+ /* Did a command mangle enough of the buffer refresh that we
+ should repaint the screen */
+
+const shortcut *currshortcut;
+ /* The current shortcut list we're using. */
+int currmenu;
+ /* The currently loaded menu */
+
+sc *sclist = NULL;
+ /* New shortcut key struct */
+subnfunc *allfuncs = NULL;
+ /* New struct for the function list */
+
+#ifndef NANO_TINY
+filestruct *search_history = NULL;
+ /* The search string history list. */
+filestruct *searchage = NULL;
+ /* The top of the search string history list. */
+filestruct *searchbot = NULL;
+ /* The bottom of the search string history list. */
+filestruct *replace_history = NULL;
+ /* The replace string history list. */
+filestruct *replaceage = NULL;
+ /* The top of the replace string history list. */
+filestruct *replacebot = NULL;
+ /* The bottom of the replace string history list. */
+poshiststruct *poshistory;
+ /* The cursor position history list */
+#endif
+
+/* Regular expressions. */
+#ifdef HAVE_REGEX_H
+regex_t search_regexp;
+ /* The compiled regular expression to use in searches. */
+regmatch_t regmatches[10];
+ /* The match positions for parenthetical subexpressions, 10
+ * maximum, used in regular expression searches. */
+#endif
+
+int reverse_attr = A_REVERSE;
+ /* The curses attribute we use for reverse video. */
+
+char *homedir = NULL;
+ /* The user's home directory, from $HOME or /etc/passwd. */
+
+/* Return the number of entries in the shortcut list s for a given menu. */
+size_t length_of_list(int menu)
+{
+ subnfunc *f;
+ size_t i = 0;
+
+ for (f = allfuncs; f != NULL; f = f->next)
+ if ((f->menus & menu) != 0
+#ifndef DISABLE_HELP
+ && strlen(f->help) > 0
+#endif
+ ) {
+ i++;
+ }
+ return i;
+}
+
+/* Just throw this here */
+void case_sens_void(void)
+{
+}
+void regexp_void(void)
+{
+}
+void gototext_void(void)
+{
+}
+void to_files_void(void)
+{
+}
+void dos_format_void(void)
+{
+}
+void mac_format_void(void)
+{
+}
+void append_void(void)
+{
+}
+void prepend_void(void)
+{
+}
+void backup_file_void(void)
+{
+}
+void new_buffer_void(void)
+{
+}
+void backwards_void(void)
+{
+}
+void goto_dir_void(void)
+{
+}
+void no_replace_void(void)
+{
+}
+void ext_cmd_void(void)
+{
+}
+
+/* Set type of function based on the string */
+function_type strtokeytype(const char *str)
+{
+ if (str[0] == 'M' || str[0] == 'm')
+ return META;
+ else if (str[0] == '^')
+ return CONTROL;
+ else if (str[0] == 'F' || str[0] == 'F')
+ return FKEY;
+ else
+ return RAWINPUT;
+}
+
+/* Add a string to the new function list strict.
+ Does not allow updates, yet anyway */
+void add_to_funcs(void (*func)(void), int menus, const char *desc, const char *help,
+ bool blank_after, bool viewok)
+{
+ subnfunc *f;
+
+ if (allfuncs == NULL) {
+ allfuncs = (subnfunc *) nmalloc(sizeof(subnfunc));
+ f = allfuncs;
+ } else {
+ for (f = allfuncs; f->next != NULL; f = f->next)
+ ;
+ f->next = (subnfunc *)nmalloc(sizeof(subnfunc));
+ f = f->next;
+ }
+ f->next = NULL;
+ f->scfunc = func;
+ f->menus = menus;
+ f->desc = desc;
+ f->viewok = viewok;
+#ifndef DISABLE_HELP
+ f->help = help;
+ f->blank_after = blank_after;
+#endif
+
+#ifdef DEBUG
+ fprintf(stderr, "Added func \"%s\"", f->desc);
+#endif
+}
+
+const sc *first_sc_for(int menu, void (*func)(void)) {
+ const sc *s;
+ const sc *metasc = NULL;
+
+ for (s = sclist; s != NULL; s = s->next) {
+ if ((s->menu & menu) && s->scfunc == func) {
+ /* try to use a meta sequence as a last resort. Otherwise
+ we will run into problems when we try and handle things like
+ the arrow keys, home, etc, if for some reason the user bound
+ them to a meta sequence first *shrug* */
+ if (s->type == META) {
+ metasc = s;
+ continue;
+ } /* otherwise it was something else, use it */
+ return s;
+ }
+ }
+
+ /* If we're here we may have found only meta sequences, if so use one */
+ if (metasc)
+ return metasc;
+
+#ifdef DEBUG
+ fprintf(stderr, "Whoops, returning null given func %ld in menu %d\n", (long) func, menu);
+#endif
+ /* Otherwise... */
+ return NULL;
+}
+
+
+/* Add a string to the new shortcut list implementation
+ Allows updates to existing entries in the list */
+void add_to_sclist(int menu, const char *scstring, void (*func)(void), int toggle, int execute)
+{
+ sc *s;
+
+ if (sclist == NULL) {
+ sclist = (sc *) nmalloc(sizeof(sc));
+ s = sclist;
+ s->next = NULL;
+ } else {
+ for (s = sclist; s->next != NULL; s = s->next)
+ if (s->menu == menu && s->keystr == scstring)
+ break;
+
+ if (s->menu != menu || s->keystr != scstring) { /* i.e. this is not a replace... */
+#ifdef DEBUG
+ fprintf(stderr, "No match found...\n");
+#endif
+ s->next = (sc *)nmalloc(sizeof(sc));
+ s = s->next;
+ s->next = NULL;
+ }
+ }
+
+ s->type = strtokeytype(scstring);
+ s->menu = menu;
+ s->toggle = toggle;
+ s->keystr = (char *) scstring;
+ s->scfunc = func;
+ s->execute = execute;
+ assign_keyinfo(s);
+
+#ifdef DEBUG
+ fprintf(stderr, "list val = %d\n", (int) s->menu);
+ fprintf(stderr, "Hey, set sequence to %d for shortcut \"%s\"\n", s->seq, scstring);
+#endif
+}
+
+/* Return the given menu's first shortcut sequence, or the default value
+ (2nd arg). Assumes currmenu for the menu to check*/
+int sc_seq_or (void (*func)(void), int defaultval)
+{
+ const sc *s = first_sc_for(currmenu, func);
+
+ if (s)
+ return s->seq;
+ /* else */
+ return defaultval;
+
+}
+
+/* Assign the info to the shortcut struct
+ Assumes keystr is already assigned, naturally */
+void assign_keyinfo(sc *s)
+{
+ if (s->type == CONTROL) {
+ assert(strlen(s->keystr) > 1);
+ s->seq = s->keystr[1] - 64;
+ } else if (s->type == META) {
+ assert(strlen(s->keystr) > 2);
+ s->seq = tolower((int) s->keystr[2]);
+ } else if (s->type == FKEY) {
+ assert(strlen(s->keystr) > 1);
+ s->seq = KEY_F0 + atoi(&s->keystr[1]);
+ } else /* RAWINPUT */
+ s->seq = (int) s->keystr[0];
+
+ /* Override some keys which don't bind as nicely as we'd like */
+ if (s->type == CONTROL && (!strcasecmp(&s->keystr[1], "space")))
+ s->seq = 0;
+ else if (s->type == META && (!strcasecmp(&s->keystr[2], "space")))
+ s->seq = (int) ' ';
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kup")))
+ s->seq = KEY_UP;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kdown")))
+ s->seq = KEY_DOWN;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kleft")))
+ s->seq = KEY_LEFT;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kright")))
+ s->seq = KEY_RIGHT;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kinsert")))
+ s->seq = KEY_IC;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kdel")))
+ s->seq = KEY_DC;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kbsp")))
+ s->seq = KEY_BACKSPACE;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kenter")))
+ s->seq = KEY_ENTER;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kpup")))
+ s->seq = KEY_PPAGE;
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kpdown")))
+ s->seq = KEY_NPAGE;
+#ifdef KEY_HOME
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "khome")))
+ s->seq = KEY_HOME;
+#endif
+#ifdef KEY_END
+ else if (s->type == RAWINPUT && (!strcasecmp(s->keystr, "kend")))
+ s->seq = KEY_END;
+#endif
+
+}
+
+#ifdef DEBUG
+
+void print_sclist(void)
+{
+ sc *s;
+ const subnfunc *f;
+
+ for (s = sclist; s->next != NULL; s = s->next) {
+ f = sctofunc(s);
+ if (f)
+ fprintf(stderr, "Shortcut \"%s\", function: %s, menus %d\n", s->keystr, f->desc, f->menus);
+ else
+ fprintf(stderr, "Hmm, didnt find a func for \"%s\"\n", s->keystr);
+ }
+
+}
+#endif
+
+
+/* Stuff we need to make at least static here so we can access it below */
+/* TRANSLATORS: Try to keep the next five strings at most 10 characters. */
+const char *cancel_msg = N_("Cancel");
+const char *replace_msg = N_("Replace");
+const char *no_replace_msg = N_("No Replace");
+
+#ifndef NANO_TINY
+const char *case_sens_msg = N_("Case Sens");
+const char *backwards_msg = N_("Backwards");
+#endif
+
+#ifdef HAVE_REGEX_H
+const char *regexp_msg = N_("Regexp");
+#endif
+
+/* Stuff we want to just stun out if we're in TINY mode */
+#ifdef NANO_TINY
+const char *gototext_msg = "";
+const char *do_para_begin_msg = "";
+const char *do_para_end_msg = "";
+const char *case_sens_msg = "";
+const char *backwards_msg = "";
+const char *do_cut_till_end = "";
+const char *dos_format_msg = "";
+const char *mac_format_msg = "";
+const char *append_msg = "";
+const char *prepend_msg = "";
+const char *backup_file_msg = "";
+const char *to_files_msg = "";
+const char *first_file_msg = "";
+const char *whereis_next_msg = "";
+const char *last_file_msg = "";
+const char *new_buffer_msg = "";
+const char *goto_dir_msg;
+const char *ext_cmd_msg = "";
+
+#else
+/* TRANSLATORS: Try to keep the next five strings at most 10 characters. */
+const char *prev_history_msg = N_("PrevHstory");
+const char *next_history_msg = N_("NextHstory");
+const char *gototext_msg = N_("Go To Text");
+/* TRANSLATORS: Try to keep the next three strings at most 12 characters. */
+const char *whereis_next_msg = N_("WhereIs Next");
+#ifndef DISABLE_BROWSER
+const char *first_file_msg = N_("First File");
+const char *last_file_msg = N_("Last File");
+/* TRANSLATORS: Try to keep the next nine strings at most 16 characters. */
+const char *to_files_msg = N_("To Files");
+#endif
+const char *dos_format_msg = N_("DOS Format");
+const char *mac_format_msg = N_("Mac Format");
+const char *append_msg = N_("Append");
+const char *prepend_msg = N_("Prepend");
+const char *backup_file_msg = N_("Backup File");
+const char *ext_cmd_msg = N_("Execute Command");
+#ifdef ENABLE_MULTIBUFFER
+const char *new_buffer_msg = N_("New Buffer");
+#endif
+const char *goto_dir_msg = N_("Go To Dir");
+
+#endif /* NANO_TINY */
+
+/* Initialize all shortcut lists. If unjustify is TRUE, replace the
+ * Uncut shortcut in the main shortcut list with UnJustify. */
+void shortcut_init(bool unjustify)
+{
+ /* TRANSLATORS: Try to keep the following strings at most 10 characters. */
+ const char *get_help_msg = N_("Get Help");
+ const char *exit_msg = N_("Exit");
+ const char *whereis_msg = N_("Where Is");
+ const char *prev_page_msg = N_("Prev Page");
+ const char *next_page_msg = N_("Next Page");
+ const char *first_line_msg = N_("First Line");
+ const char *last_line_msg = N_("Last Line");
+ const char *suspend_msg = N_("Suspend");
+#ifndef DISABLE_JUSTIFY
+ const char *beg_of_par_msg = N_("Beg of Par");
+ const char *end_of_par_msg = N_("End of Par");
+ const char *fulljstify_msg = N_("FullJstify");
+#endif
+ const char *refresh_msg = N_("Refresh");
+#ifndef NANO_TINY
+ const char *insert_file_msg = N_("Insert File");
+#endif
+ const char *go_to_line_msg = N_("Go To Line");
+
+#ifndef DISABLE_JUSTIFY
+ const char *nano_justify_msg = N_("Justify the current paragraph");
+#endif
+#ifndef DISABLE_HELP
+ /* TRANSLATORS: The next long series of strings are shortcut descriptions;
+ * they are best kept shorter than 56 characters, but may be longer. */
+ const char *nano_cancel_msg = N_("Cancel the current function");
+ const char *nano_help_msg = N_("Display this help text");
+ const char *nano_exit_msg =
+#ifdef ENABLE_MULTIBUFFER
+ N_("Close the current file buffer / Exit from nano")
+#else
+ N_("Exit from nano")
+#endif
+ ;
+ const char *nano_writeout_msg =
+ N_("Write the current file to disk");
+ const char *nano_insert_msg =
+ N_("Insert another file into the current one");
+ const char *nano_whereis_msg =
+ N_("Search for a string or a regular expression");
+ const char *nano_prevpage_msg = N_("Go to previous screen");
+ const char *nano_nextpage_msg = N_("Go to next screen");
+ const char *nano_cut_msg =
+ N_("Cut the current line and store it in the cutbuffer");
+ const char *nano_uncut_msg =
+ N_("Uncut from the cutbuffer into the current line");
+ const char *nano_cursorpos_msg =
+ N_("Display the position of the cursor");
+ const char *nano_spell_msg =
+ N_("Invoke the spell checker, if available");
+ const char *nano_replace_msg =
+ N_("Replace a string or a regular expression");
+ const char *nano_gotoline_msg = N_("Go to line and column number");
+#ifndef NANO_TINY
+ const char *nano_mark_msg = N_("Mark text at the cursor position");
+ const char *nano_whereis_next_msg = N_("Repeat last search");
+ const char *nano_copy_msg =
+ N_("Copy the current line and store it in the cutbuffer");
+ const char *nano_indent_msg = N_("Indent the current line");
+ const char *nano_unindent_msg = N_("Unindent the current line");
+ const char *nano_undo_msg = N_("Undo the last operation");
+ const char *nano_redo_msg = N_("Redo the last undone operation");
+#endif
+ const char *nano_forward_msg = N_("Go forward one character");
+ const char *nano_back_msg = N_("Go back one character");
+#ifndef NANO_TINY
+ const char *nano_nextword_msg = N_("Go forward one word");
+ const char *nano_prevword_msg = N_("Go back one word");
+#endif
+ const char *nano_prevline_msg = N_("Go to previous line");
+ const char *nano_nextline_msg = N_("Go to next line");
+ const char *nano_home_msg = N_("Go to beginning of current line");
+ const char *nano_end_msg = N_("Go to end of current line");
+#ifndef DISABLE_JUSTIFY
+ const char *nano_parabegin_msg =
+ N_("Go to beginning of paragraph; then of previous paragraph");
+ const char *nano_paraend_msg =
+ N_("Go just beyond end of paragraph; then of next paragraph");
+#endif
+ const char *nano_firstline_msg =
+ N_("Go to the first line of the file");
+ const char *nano_lastline_msg =
+ N_("Go to the last line of the file");
+#ifndef NANO_TINY
+ const char *nano_bracket_msg = N_("Go to the matching bracket");
+ const char *nano_scrollup_msg =
+ N_("Scroll up one line without scrolling the cursor");
+ const char *nano_scrolldown_msg =
+ N_("Scroll down one line without scrolling the cursor");
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ const char *nano_prevfile_msg =
+ N_("Switch to the previous file buffer");
+ const char *nano_nextfile_msg =
+ N_("Switch to the next file buffer");
+#endif
+ const char *nano_verbatim_msg =
+ N_("Insert the next keystroke verbatim");
+ const char *nano_tab_msg =
+ N_("Insert a tab at the cursor position");
+ const char *nano_enter_msg =
+ N_("Insert a newline at the cursor position");
+ const char *nano_delete_msg =
+ N_("Delete the character under the cursor");
+ const char *nano_backspace_msg =
+ N_("Delete the character to the left of the cursor");
+#ifndef NANO_TINY
+ const char *nano_cut_till_end_msg =
+ N_("Cut from the cursor position to the end of the file");
+#endif
+#ifndef DISABLE_JUSTIFY
+ const char *nano_fulljustify_msg = N_("Justify the entire file");
+#endif
+#ifndef NANO_TINY
+ const char *nano_wordcount_msg =
+ N_("Count the number of words, lines, and characters");
+#endif
+ const char *nano_refresh_msg =
+ N_("Refresh (redraw) the current screen");
+ const char *nano_suspend_msg =
+ N_("Suspend the editor (if suspend is enabled)");
+#ifndef NANO_TINY
+ const char *nano_case_msg =
+ N_("Toggle the case sensitivity of the search");
+ const char *nano_reverse_msg =
+ N_("Reverse the direction of the search");
+#endif
+#ifdef HAVE_REGEX_H
+ const char *nano_regexp_msg =
+ N_("Toggle the use of regular expressions");
+#endif
+#ifndef NANO_TINY
+ const char *nano_prev_history_msg =
+ N_("Recall the previous search/replace string");
+ const char *nano_next_history_msg =
+ N_("Recall the next search/replace string");
+#endif
+#ifndef DISABLE_BROWSER
+ const char *nano_tofiles_msg = N_("Go to file browser");
+#endif
+#ifndef NANO_TINY
+ const char *nano_dos_msg = N_("Toggle the use of DOS format");
+ const char *nano_mac_msg = N_("Toggle the use of Mac format");
+#endif
+ const char *nano_append_msg = N_("Toggle appending");
+ const char *nano_prepend_msg = N_("Toggle prepending");
+#ifndef NANO_TINY
+ const char *nano_backup_msg =
+ N_("Toggle backing up of the original file");
+ const char *nano_execute_msg = N_("Execute external command");
+#endif
+#if !defined(NANO_TINY) && defined(ENABLE_MULTIBUFFER)
+ const char *nano_multibuffer_msg =
+ N_("Toggle the use of a new buffer");
+#endif
+#ifndef DISABLE_BROWSER
+ const char *nano_exitbrowser_msg = N_("Exit from the file browser");
+ const char *nano_firstfile_msg =
+ N_("Go to the first file in the list");
+ const char *nano_lastfile_msg =
+ N_("Go to the last file in the list");
+ const char *nano_forwardfile_msg = N_("Go to the next file in the list");
+ const char *nano_backfile_msg = N_("Go to the previous file in the list");
+ const char *nano_gotodir_msg = N_("Go to directory");
+#endif
+#endif /* !DISABLE_HELP */
+
+#ifndef DISABLE_HELP
+#define IFSCHELP(help) help
+#else
+#define IFSCHELP(help) ""
+#endif
+
+ while (allfuncs != NULL) {
+ subnfunc *f = allfuncs;
+ allfuncs = (allfuncs)->next;
+ free(f);
+ }
+
+ add_to_funcs(do_help_void,
+ (MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MBROWSER|MWHEREISFILE|MGOTODIR),
+ get_help_msg, IFSCHELP(nano_help_msg), FALSE, VIEW);
+
+ add_to_funcs( do_cancel,
+ (MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MWHEREISFILE|MGOTODIR|MYESNO),
+ cancel_msg, IFSCHELP(nano_cancel_msg), FALSE, VIEW);
+
+ add_to_funcs(do_exit, MMAIN,
+#ifdef ENABLE_MULTIBUFFER
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ openfile != NULL && openfile != openfile->next ? N_("Close") :
+#endif
+ exit_msg, IFSCHELP(nano_exit_msg), FALSE, VIEW);
+
+#ifndef DISABLE_BROWSER
+ add_to_funcs(do_exit, MBROWSER, exit_msg, IFSCHELP(nano_exitbrowser_msg), FALSE, VIEW);
+#endif
+
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_writeout_void, MMAIN, N_("WriteOut"),
+ IFSCHELP(nano_writeout_msg), FALSE, NOVIEW);
+
+#ifndef DISABLE_JUSTIFY
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_justify_void, MMAIN, N_("Justify"),
+ nano_justify_msg, TRUE, NOVIEW);
+#endif
+
+ /* We allow inserting files in view mode if multibuffers are
+ * available, so that we can view multiple files. If we're using
+ * restricted mode, inserting files is disabled, since it allows
+ * reading from or writing to files not specified on the command
+ * line. */
+
+ add_to_funcs(do_insertfile_void,
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ MMAIN, N_("Read File"), IFSCHELP(nano_insert_msg), FALSE,
+#ifdef ENABLE_MULTIBUFFER
+ VIEW);
+#else
+ NOVIEW);
+#endif
+
+ add_to_funcs(do_search, MMAIN|MBROWSER, whereis_msg,
+ IFSCHELP(nano_whereis_msg), FALSE, VIEW);
+
+ add_to_funcs(do_page_up, MMAIN|MHELP|MBROWSER,
+ prev_page_msg, IFSCHELP(nano_prevpage_msg), FALSE, VIEW);
+ add_to_funcs(do_page_down, MMAIN|MHELP|MBROWSER,
+ next_page_msg, IFSCHELP(nano_nextpage_msg), TRUE, VIEW);
+
+
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_cut_text_void, MMAIN, N_("Cut Text"), IFSCHELP(nano_cut_msg),
+ FALSE, NOVIEW);
+
+ if (unjustify)
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_uncut_text, MMAIN, N_("UnJustify"), "",
+ FALSE, NOVIEW);
+
+ else
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_uncut_text, MMAIN, N_("UnCut Text"), IFSCHELP(nano_uncut_msg),
+ FALSE, NOVIEW);
+
+#ifndef NANO_TINY
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_cursorpos_void, MMAIN, N_("Cur Pos"), IFSCHELP(nano_cursorpos_msg),
+ FALSE, VIEW);
+#endif
+
+ /* If we're using restricted mode, spell checking is disabled
+ * because it allows reading from or writing to files not specified
+ * on the command line. */
+#ifndef DISABLE_SPELLER
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_spell, MMAIN, N_("To Spell"), IFSCHELP(nano_spell_msg),
+ TRUE, NOVIEW);
+#endif
+
+ add_to_funcs(do_first_line,
+ (MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE),
+ first_line_msg, IFSCHELP(nano_firstline_msg), FALSE, VIEW);
+
+ add_to_funcs(do_last_line,
+ (MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE),
+ last_line_msg, IFSCHELP(nano_lastline_msg), TRUE, VIEW);
+
+
+ add_to_funcs(do_gotolinecolumn_void, (MMAIN|MWHEREIS),
+ go_to_line_msg, IFSCHELP(nano_gotoline_msg), FALSE, VIEW);
+
+#ifdef NANO_TINY
+ /* TRANSLATORS: Try to keep this at most 10 characters. */
+ add_to_funcs(do_cursorpos_void, MMAIN, N_("Cur Pos"), IFSCHELP(nano_cursorpos_msg),
+ FALSE, VIEW);
+#endif
+
+
+ add_to_funcs(do_replace, (MMAIN|MWHEREIS), replace_msg, IFSCHELP(nano_replace_msg),
+
+#ifndef NANO_TINY
+ FALSE,
+#else
+ TRUE,
+#endif
+ NOVIEW);
+
+#ifndef NANO_TINY
+
+ add_to_funcs(do_mark, MMAIN, N_("Mark Text"),
+ IFSCHELP(nano_mark_msg), FALSE, VIEW);
+
+ add_to_funcs(do_research, (MMAIN|MBROWSER), whereis_next_msg,
+ IFSCHELP(nano_whereis_next_msg), TRUE, VIEW);
+
+ add_to_funcs(do_copy_text, MMAIN, N_("Copy Text"),
+ IFSCHELP(nano_copy_msg), FALSE, NOVIEW);
+
+ add_to_funcs(do_indent_void, MMAIN, N_("Indent Text"),
+ IFSCHELP(nano_indent_msg), FALSE, NOVIEW);
+
+ add_to_funcs(do_unindent, MMAIN, N_("Unindent Text"),
+ IFSCHELP(nano_unindent_msg), FALSE, NOVIEW);
+
+ if (ISSET(UNDOABLE)) {
+ add_to_funcs(do_undo, MMAIN, N_("Undo"),
+ IFSCHELP(nano_undo_msg), FALSE, NOVIEW);
+
+ add_to_funcs(do_redo, MMAIN, N_("Redo"),
+ IFSCHELP(nano_redo_msg), TRUE, NOVIEW);
+ }
+
+#endif
+
+ add_to_funcs(do_right, MMAIN, N_("Forward"), IFSCHELP(nano_forward_msg),
+ FALSE, VIEW);
+
+#ifndef DISABLE_BROWSER
+ add_to_funcs(do_right, MBROWSER, N_("Forward"), IFSCHELP(nano_forwardfile_msg),
+ FALSE, VIEW);
+#endif
+
+ add_to_funcs(do_right, MALL, "", "", FALSE, VIEW);
+
+ add_to_funcs(do_left, MMAIN, N_("Back"), IFSCHELP(nano_back_msg),
+ FALSE, VIEW);
+
+#ifndef DISABLE_BROWSER
+ add_to_funcs(do_left, MBROWSER, N_("Back"), IFSCHELP(nano_backfile_msg),
+ FALSE, VIEW);
+#endif
+
+ add_to_funcs(do_left, MALL, "", "", FALSE, VIEW);
+
+#ifndef NANO_TINY
+ add_to_funcs(do_next_word_void, MMAIN, N_("Next Word"),
+ IFSCHELP(nano_nextword_msg), FALSE, VIEW);
+
+ add_to_funcs(do_prev_word_void, MMAIN, N_("Prev Word"),
+ IFSCHELP(nano_prevword_msg), FALSE, VIEW);
+#endif
+
+ add_to_funcs(do_up_void, (MMAIN|MHELP|MBROWSER), N_("Prev Line"),
+ IFSCHELP(nano_prevline_msg), FALSE, VIEW);
+
+ add_to_funcs(do_down_void, (MMAIN|MHELP|MBROWSER), N_("Next Line"),
+ IFSCHELP(nano_nextline_msg), TRUE, VIEW);
+
+ add_to_funcs(do_home, MMAIN, N_("Home"), IFSCHELP(nano_home_msg),
+ FALSE, VIEW);
+
+ add_to_funcs(do_end, MMAIN, N_("End"), IFSCHELP(nano_end_msg),
+ FALSE, VIEW);
+
+#ifndef DISABLE_JUSTIFY
+ add_to_funcs(do_para_begin_void, (MMAIN|MWHEREIS), beg_of_par_msg,
+ IFSCHELP(nano_parabegin_msg), FALSE, VIEW);
+
+ add_to_funcs(do_para_end_void, (MMAIN|MWHEREIS), end_of_par_msg,
+ IFSCHELP(nano_paraend_msg), FALSE, VIEW);
+#endif
+
+#ifndef NANO_TINY
+ add_to_funcs(do_find_bracket, MMAIN, _("Find Other Bracket"),
+ IFSCHELP(nano_bracket_msg), FALSE, VIEW);
+
+ add_to_funcs(do_scroll_up, MMAIN, N_("Scroll Up"),
+ IFSCHELP(nano_scrollup_msg), FALSE, VIEW);
+
+ add_to_funcs(do_scroll_down, MMAIN, N_("Scroll Down"),
+ IFSCHELP(nano_scrolldown_msg), FALSE, VIEW);
+#endif
+
+#ifdef ENABLE_MULTIBUFFER
+ add_to_funcs(switch_to_prev_buffer_void, MMAIN, _("Previous File"),
+ IFSCHELP(nano_prevfile_msg), FALSE, VIEW);
+ add_to_funcs(switch_to_next_buffer_void, MMAIN, N_("Next File"),
+ IFSCHELP(nano_nextfile_msg), TRUE, VIEW);
+#endif
+
+ add_to_funcs(do_verbatim_input, MMAIN, N_("Verbatim Input"),
+ IFSCHELP(nano_verbatim_msg), FALSE, NOVIEW);
+ add_to_funcs(do_verbatim_input, MWHEREIS|MREPLACE|MREPLACE2|MEXTCMD|MSPELL,
+ "", "", FALSE, NOVIEW);
+
+ add_to_funcs(do_tab, MMAIN, N_("Tab"), IFSCHELP(nano_tab_msg),
+ FALSE, NOVIEW);
+ add_to_funcs(do_tab, MALL, "", "", FALSE, NOVIEW);
+ add_to_funcs(do_enter_void, MMAIN, N_("Enter"), IFSCHELP(nano_enter_msg),
+ FALSE, NOVIEW);
+ add_to_funcs(do_enter_void, MALL, "", "", FALSE, NOVIEW);
+ add_to_funcs(do_delete, MMAIN, N_("Delete"), IFSCHELP(nano_delete_msg),
+ FALSE, NOVIEW);
+ add_to_funcs(do_delete, MALL, "", "", FALSE, NOVIEW);
+ add_to_funcs(do_backspace, MMAIN, N_("Backspace"), IFSCHELP(nano_backspace_msg),
+#ifndef NANO_TINY
+ FALSE,
+#else
+ TRUE,
+#endif
+ NOVIEW);
+
+ add_to_funcs(do_backspace, MALL, "", "",
+#ifndef NANO_TINY
+ FALSE,
+#else
+ TRUE,
+#endif
+ NOVIEW);
+
+#ifndef NANO_TINY
+ add_to_funcs(do_cut_till_end, MMAIN, N_("CutTillEnd"),
+ IFSCHELP(nano_cut_till_end_msg), TRUE, NOVIEW);
+#endif
+
+ add_to_funcs(xon_complaint, MMAIN, "", "", FALSE, VIEW);
+ add_to_funcs(xoff_complaint, MMAIN, "", "", FALSE, VIEW);
+
+#ifndef DISABLE_JUSTIFY
+ add_to_funcs(do_full_justify, (MMAIN|MWHEREIS), fulljstify_msg,
+ IFSCHELP(nano_fulljustify_msg), FALSE, NOVIEW);
+#endif
+
+#ifndef NANO_TINY
+ add_to_funcs(do_wordlinechar_count, MMAIN, N_("Word Count"),
+ IFSCHELP(nano_wordcount_msg), FALSE, VIEW);
+#endif
+
+ add_to_funcs(total_refresh, (MMAIN|MHELP), refresh_msg,
+ IFSCHELP(nano_refresh_msg), FALSE, VIEW);
+
+ add_to_funcs(do_suspend_void, MMAIN, suspend_msg,
+ IFSCHELP(nano_suspend_msg), TRUE, VIEW);
+
+#ifndef NANO_TINY
+ add_to_funcs(case_sens_void,
+ (MWHEREIS|MREPLACE|MWHEREISFILE),
+ case_sens_msg, IFSCHELP(nano_case_msg), FALSE, VIEW);
+
+ add_to_funcs(backwards_void,
+ (MWHEREIS|MREPLACE|MWHEREISFILE),
+ backwards_msg, IFSCHELP(nano_reverse_msg), FALSE, VIEW);
+#endif
+
+#ifdef HAVE_REGEX_H
+ add_to_funcs(regexp_void,
+ (MWHEREIS|MREPLACE|MWHEREISFILE),
+ regexp_msg, IFSCHELP(nano_regexp_msg), FALSE, VIEW);
+#endif
+
+#ifndef NANO_TINY
+ add_to_funcs(get_history_older_void,
+ (MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE),
+ prev_history_msg, IFSCHELP(nano_prev_history_msg), FALSE, VIEW);
+
+ add_to_funcs(get_history_newer_void,
+ (MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE),
+ next_history_msg, IFSCHELP(nano_next_history_msg), FALSE, VIEW);
+#endif
+
+ add_to_funcs(no_replace_void, MREPLACE,
+ no_replace_msg, IFSCHELP(nano_whereis_msg), FALSE, VIEW);
+
+ add_to_funcs(gototext_void, MGOTOLINE,
+ gototext_msg, IFSCHELP(nano_whereis_msg), TRUE, VIEW);
+
+#ifndef DISABLE_BROWSER
+ if (!ISSET(RESTRICTED))
+ add_to_funcs(to_files_void,
+ (MGOTOLINE|MINSERTFILE),
+ to_files_msg, IFSCHELP(nano_tofiles_msg), FALSE, VIEW);
+#endif
+
+#ifndef NANO_TINY
+ /* If we're using restricted mode, the DOS format, Mac format,
+ * append, prepend, and backup toggles are disabled. The first and
+ * second are useless since inserting files is disabled, the third
+ * and fourth are disabled because they allow writing to files not
+ * specified on the command line, and the fifth is useless since
+ * backups are disabled. */
+ if (!ISSET(RESTRICTED))
+ add_to_funcs(dos_format_void, MWRITEFILE,
+ dos_format_msg, IFSCHELP(nano_dos_msg), FALSE, NOVIEW);
+
+ if (!ISSET(RESTRICTED))
+ add_to_funcs(mac_format_void, MWRITEFILE,
+ mac_format_msg, IFSCHELP(nano_mac_msg), FALSE, NOVIEW);
+
+ if (!ISSET(RESTRICTED))
+ add_to_funcs( append_void, MWRITEFILE,
+ append_msg, IFSCHELP(nano_append_msg), FALSE, NOVIEW);
+
+ if (!ISSET(RESTRICTED))
+ add_to_funcs( prepend_void, MWRITEFILE,
+ prepend_msg, IFSCHELP(nano_prepend_msg), FALSE, NOVIEW);
+
+ if (!ISSET(RESTRICTED))
+ add_to_funcs( backup_file_void, MWRITEFILE,
+ backup_file_msg, IFSCHELP(nano_backup_msg), FALSE, NOVIEW);
+#endif
+
+#ifndef NANO_TINY
+ /* If we're using restricted mode, command execution is disabled.
+ * It's useless since inserting files is disabled. */
+ if (!ISSET(RESTRICTED))
+ add_to_funcs( ext_cmd_void, MINSERTFILE,
+ ext_cmd_msg, IFSCHELP(nano_execute_msg), FALSE, NOVIEW);
+
+#ifdef ENABLE_MULTIBUFFER
+ /* If we're using restricted mode, the multibuffer toggle is
+ * disabled. It's useless since inserting files is disabled. */
+ if (!ISSET(RESTRICTED))
+ add_to_funcs( new_buffer_void, MINSERTFILE,
+ new_buffer_msg, IFSCHELP(nano_multibuffer_msg), FALSE, NOVIEW);
+#endif
+
+ add_to_funcs( do_insertfile_void, MEXTCMD,
+ insert_file_msg, IFSCHELP(nano_insert_msg), FALSE, VIEW);
+
+#ifdef ENABLE_MULTIBUFFER
+ add_to_funcs( new_buffer_void, MEXTCMD,
+ new_buffer_msg, IFSCHELP(nano_multibuffer_msg), FALSE, NOVIEW);
+#endif
+#endif
+
+#ifndef DISABLE_HELP
+ add_to_funcs(edit_refresh, MHELP,
+ refresh_msg, nano_refresh_msg, FALSE, VIEW);
+
+ add_to_funcs(do_exit, MHELP, exit_msg, IFSCHELP(nano_exit_msg), FALSE, VIEW);
+
+
+#endif
+
+#ifndef DISABLE_BROWSER
+
+ add_to_funcs(do_first_file,
+ (MBROWSER|MWHEREISFILE),
+ first_file_msg, IFSCHELP(nano_firstfile_msg), FALSE, VIEW);
+
+ add_to_funcs(do_last_file,
+ (MBROWSER|MWHEREISFILE),
+ last_file_msg, IFSCHELP(nano_lastfile_msg), FALSE, VIEW);
+
+ add_to_funcs(goto_dir_void, MBROWSER,
+ goto_dir_msg, IFSCHELP(nano_gotodir_msg), FALSE, VIEW);
+#endif
+
+ currmenu = MMAIN;
+
+ add_to_sclist(MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MBROWSER|MWHEREISFILE|MGOTODIR,
+ "^G", do_help_void, 0, TRUE);
+ add_to_sclist(MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MBROWSER|MWHEREISFILE|MGOTODIR,
+ "F1", do_help_void, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "^X", do_exit, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "F2", do_exit, 0, TRUE);
+ add_to_sclist(MMAIN, "^_", do_gotolinecolumn_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F13", do_gotolinecolumn_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M-G", do_gotolinecolumn_void, 0, TRUE);
+ add_to_sclist(MMAIN, "^O", do_writeout_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F3", do_writeout_void, 0, TRUE);
+#ifndef DISABLE_JUSTIFY
+ add_to_sclist(MMAIN, "^J", do_justify_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F4", do_justify_void, 0, TRUE);
+#endif
+ add_to_sclist(MMAIN, "^R", do_insertfile_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F5", do_insertfile_void, 0, TRUE);
+ add_to_sclist(MMAIN, "kinsert", do_insertfile_void, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER, "^W", do_search, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER, "F6", do_search, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "^Y", do_page_up, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "F7", do_page_up, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "kpup", do_page_up, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "^V", do_page_down, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "F8", do_page_down, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER|MHELP|MWHEREISFILE, "kpdown", do_page_down, 0, TRUE);
+ add_to_sclist(MMAIN, "^K", do_cut_text_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F9", do_cut_text_void, 0, TRUE);
+ add_to_sclist(MMAIN, "^U", do_uncut_text, 0, TRUE);
+ add_to_sclist(MMAIN, "F10", do_uncut_text, 0, TRUE);
+ add_to_sclist(MMAIN, "^C", do_cursorpos_void, 0, TRUE);
+ add_to_sclist(MMAIN, "F11", do_cursorpos_void, 0, TRUE);
+#ifndef DISABLE_SPELLER
+ add_to_sclist(MMAIN, "^T", do_spell, 0, TRUE);
+ add_to_sclist(MMAIN, "F12", do_spell, 0, TRUE);
+#endif
+ add_to_sclist(MMAIN, "^\\", do_replace, 0, TRUE);
+ add_to_sclist(MMAIN, "F14", do_replace, 0, TRUE);
+ add_to_sclist(MMAIN, "M-R", do_replace, 0, TRUE);
+ add_to_sclist(MWHEREIS, "^R", do_replace, 0, FALSE);
+ add_to_sclist(MREPLACE, "^R", no_replace_void, 0, FALSE);
+ add_to_sclist(MWHEREIS, "^T", do_gotolinecolumn_void, 0, FALSE);
+#ifndef NANO_TINY
+ add_to_sclist(MMAIN, "^^", do_mark, 0, TRUE);
+ add_to_sclist(MMAIN, "F15", do_mark, 0, TRUE);
+ add_to_sclist(MMAIN, "M-A", do_mark, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER, "M-W", do_research, 0, TRUE);
+ add_to_sclist(MMAIN|MBROWSER, "F16", do_research, 0, TRUE);
+ add_to_sclist(MMAIN, "M-^", do_copy_text, 0, TRUE);
+ add_to_sclist(MMAIN, "M-6", do_copy_text, 0, TRUE);
+ add_to_sclist(MMAIN, "M-}", do_indent_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M-{", do_unindent, 0, TRUE);
+ if (ISSET(UNDOABLE)) {
+ add_to_sclist(MMAIN, "M-U", do_undo, 0, TRUE);
+ add_to_sclist(MMAIN, "M-E", do_redo, 0, TRUE);
+ }
+ add_to_sclist(MALL, "^F", do_right, 0, TRUE);
+ add_to_sclist(MALL, "^B", do_left, 0, TRUE);
+ add_to_sclist(MMAIN, "^Space", do_next_word_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M-Space", do_prev_word_void, 0, TRUE);
+#endif
+ add_to_sclist(MALL, "kright", do_right, 0, TRUE);
+ add_to_sclist(MALL, "kleft", do_left, 0, TRUE);
+ add_to_sclist(MMAIN, "^Q", xon_complaint, 0, TRUE);
+ add_to_sclist(MMAIN, "^S", xoff_complaint, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "^P", do_up_void, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "kup", do_up_void, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "^N", do_down_void, 0, TRUE);
+ add_to_sclist(MMAIN|MHELP|MBROWSER, "kdown", do_down_void, 0, TRUE);
+ add_to_sclist(MALL, "^A", do_home, 0, TRUE);
+ add_to_sclist(MALL, "khome", do_home, 0, TRUE);
+ add_to_sclist(MALL, "^E", do_end, 0, TRUE);
+ add_to_sclist(MALL, "kend", do_end, 0, TRUE);
+#ifndef NANO_TINY
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE, "^P", get_history_older_void, 0, FALSE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE, "kup", get_history_older_void, 0, FALSE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE, "^N", get_history_newer_void, 0, FALSE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MWHEREISFILE, "kdown", get_history_newer_void, 0, FALSE);
+#endif
+#ifndef DISABLE_JUSTIFY
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2,
+ "^W", do_para_begin_void, 0, TRUE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2,
+ "^O", do_para_end_void, 0, TRUE);
+ add_to_sclist(MALL, "M-(", do_para_begin_void, 0, TRUE);
+ add_to_sclist(MALL, "M-9", do_para_begin_void, 0, TRUE);
+ add_to_sclist(MALL, "M-)", do_para_end_void, 0, TRUE);
+ add_to_sclist(MALL, "M-0", do_para_end_void, 0, TRUE);
+#endif
+ add_to_sclist(MWHEREIS,
+ "M-C", case_sens_void, 0, FALSE);
+ add_to_sclist(MREPLACE,
+ "M-C", case_sens_void, 0, FALSE);
+ add_to_sclist(MREPLACE2,
+ "M-C", case_sens_void, 0, FALSE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2,
+ "M-B", backwards_void, 0, FALSE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2,
+ "M-R", regexp_void, 0, FALSE);
+
+ add_to_sclist(MMAIN, "M-\\", do_first_line, 0, TRUE);
+ add_to_sclist(MMAIN, "M-|", do_first_line, 0, TRUE);
+ add_to_sclist(MMAIN, "M-/", do_last_line, 0, TRUE);
+ add_to_sclist(MMAIN, "M-?", do_last_line, 0, TRUE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MHELP,
+ "^Y", do_first_line, 0, TRUE);
+ add_to_sclist(MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MHELP,
+ "^V", do_last_line, 0, TRUE);
+
+#ifndef DISABLE_BROWSER
+ add_to_sclist(MBROWSER|MWHEREISFILE, "M-\\", do_first_file, 0, TRUE);
+ add_to_sclist(MBROWSER|MWHEREISFILE, "M-|", do_first_file, 0, TRUE);
+ add_to_sclist(MBROWSER|MWHEREISFILE, "M-/", do_last_file, 0, TRUE);
+ add_to_sclist(MBROWSER|MWHEREISFILE, "M-?", do_last_file, 0, TRUE);
+#endif
+ add_to_sclist(MBROWSER|MWHEREISFILE, "^_", goto_dir_void, 0, TRUE);
+ add_to_sclist(MBROWSER|MWHEREISFILE, "F13", goto_dir_void, 0, TRUE);
+ add_to_sclist(MBROWSER|MWHEREISFILE, "M-G", goto_dir_void, 0, TRUE);
+#ifndef NANO_TINY
+ add_to_sclist(MMAIN, "M-]", do_find_bracket, 0, TRUE);
+ add_to_sclist(MMAIN, "M--", do_scroll_up, 0, TRUE);
+ add_to_sclist(MMAIN, "M-_", do_scroll_up, 0, TRUE);
+ add_to_sclist(MMAIN, "M-+", do_scroll_down, 0, TRUE);
+ add_to_sclist(MMAIN, "M-=", do_scroll_down, 0, TRUE);
+#endif
+
+#ifdef ENABLE_MULTIBUFFER
+ add_to_sclist(MMAIN, "M-<", switch_to_prev_buffer_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M-,", switch_to_prev_buffer_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M->", switch_to_next_buffer_void, 0, TRUE);
+ add_to_sclist(MMAIN, "M-.", switch_to_next_buffer_void, 0, TRUE);
+#endif
+ add_to_sclist(MALL, "M-V", do_verbatim_input, 0, TRUE);
+#ifndef NANO_TINY
+ add_to_sclist(MALL, "M-T", do_cut_till_end, 0, TRUE);
+#ifndef DISABLE_JUSTIFY
+ add_to_sclist(MALL, "M-J", do_full_justify, 0, TRUE);
+#endif
+ add_to_sclist(MMAIN, "M-D", do_wordlinechar_count, 0, TRUE);
+ add_to_sclist(MMAIN, "M-X", do_toggle_void, NO_HELP, TRUE);
+ add_to_sclist(MMAIN, "M-C", do_toggle_void, CONST_UPDATE, TRUE);
+ add_to_sclist(MMAIN, "M-O", do_toggle_void, MORE_SPACE, TRUE);
+ add_to_sclist(MMAIN, "M-S", do_toggle_void, SMOOTH_SCROLL, TRUE);
+ add_to_sclist(MMAIN, "M-P", do_toggle_void, WHITESPACE_DISPLAY, TRUE);
+ add_to_sclist(MMAIN, "M-Y", do_toggle_void, NO_COLOR_SYNTAX, TRUE);
+ add_to_sclist(MMAIN, "M-H", do_toggle_void, SMART_HOME, TRUE);
+ add_to_sclist(MMAIN, "M-I", do_toggle_void, AUTOINDENT, TRUE);
+ add_to_sclist(MMAIN, "M-K", do_toggle_void, CUT_TO_END, TRUE);
+ add_to_sclist(MMAIN, "M-L", do_toggle_void, NO_WRAP, TRUE);
+ add_to_sclist(MMAIN, "M-Q", do_toggle_void, TABS_TO_SPACES, TRUE);
+ add_to_sclist(MMAIN, "M-B", do_toggle_void, BACKUP_FILE, TRUE);
+ add_to_sclist(MMAIN, "M-F", do_toggle_void, MULTIBUFFER, TRUE);
+ add_to_sclist(MMAIN, "M-M", do_toggle_void, USE_MOUSE, TRUE);
+ add_to_sclist(MMAIN, "M-N", do_toggle_void, NO_CONVERT, TRUE);
+ add_to_sclist(MMAIN, "M-Z", do_toggle_void, SUSPEND, TRUE);
+ add_to_sclist(MMAIN, "M-$", do_toggle_void, SOFTWRAP, TRUE);
+#endif
+ add_to_sclist(MGOTOLINE, "^T", gototext_void, 0, FALSE);
+ add_to_sclist(MINSERTFILE|MEXTCMD, "M-F", new_buffer_void, 0, FALSE);
+ add_to_sclist((MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MWHEREISFILE|MGOTODIR|MYESNO),
+ "^C", do_cancel, 0, FALSE);
+ add_to_sclist(MHELP, "^X", do_exit, 0, TRUE);
+ add_to_sclist(MHELP, "F2", do_exit, 0, TRUE);
+ add_to_sclist(MWRITEFILE, "M-D", dos_format_void, 0, FALSE);
+ add_to_sclist(MWRITEFILE, "M-M", mac_format_void, 0, FALSE);
+ add_to_sclist(MWRITEFILE, "M-A", append_void, 0, FALSE);
+ add_to_sclist(MWRITEFILE, "M-P", prepend_void, 0, FALSE);
+ add_to_sclist(MWRITEFILE, "M-B", backup_file_void, 0, FALSE);
+ add_to_sclist(MWRITEFILE, "^T", to_files_void, 0, FALSE);
+ add_to_sclist(MINSERTFILE, "^T", to_files_void, 0, FALSE);
+ add_to_sclist(MINSERTFILE, "^X", ext_cmd_void, 0, FALSE);
+ add_to_sclist(MMAIN, "^Z", do_suspend_void, 0, FALSE);
+ add_to_sclist(MMAIN, "^L", total_refresh, 0, TRUE);
+ add_to_sclist(MALL, "^I", do_tab, 0, TRUE);
+ add_to_sclist(MALL, "^M", do_enter_void, 0, TRUE);
+ add_to_sclist(MALL, "kenter", do_enter_void, 0, TRUE);
+ add_to_sclist(MALL, "^D", do_delete, 0, TRUE);
+ add_to_sclist(MALL, "kdel", do_delete, 0, TRUE);
+ add_to_sclist(MALL, "^H", do_backspace, 0, TRUE);
+ add_to_sclist(MALL, "kbsp", do_backspace, 0, TRUE);
+
+#ifdef DEBUG
+ print_sclist();
+#endif
+
+}
+
+/* Free the given shortcut. */
+void free_shortcutage(shortcut **shortcutage)
+{
+ assert(shortcutage != NULL);
+
+ while (*shortcutage != NULL) {
+ shortcut *ps = *shortcutage;
+ *shortcutage = (*shortcutage)->next;
+ free(ps);
+ }
+}
+
+const subnfunc *sctofunc(sc *s)
+{
+ subnfunc *f;
+
+ for (f = allfuncs; f != NULL && s->scfunc != f->scfunc; f = f->next)
+ ;
+
+ return f;
+}
+
+#ifndef NANO_TINY
+/* Now lets come up with a single (hopefully)
+ function to get a string for each flag */
+const char *flagtostr(int flag)
+{
+ switch (flag) {
+ case NO_HELP:
+ return N_("Help mode");
+ case CONST_UPDATE:
+ return N_("Constant cursor position display");
+ case MORE_SPACE:
+ return N_("Use of one more line for editing");
+ case SMOOTH_SCROLL:
+ return N_("Smooth scrolling");
+ case WHITESPACE_DISPLAY:
+ return N_("Whitespace display");
+ case NO_COLOR_SYNTAX:
+ return N_("Color syntax highlighting");
+ case SMART_HOME:
+ return N_("Smart home key");
+ case AUTOINDENT:
+ return N_("Auto indent");
+ case CUT_TO_END:
+ return N_("Cut to end");
+ case NO_WRAP:
+ return N_("Long line wrapping");
+ case TABS_TO_SPACES:
+ return N_("Conversion of typed tabs to spaces");
+ case BACKUP_FILE:
+ return N_("Backup files");
+ case MULTIBUFFER:
+ return N_("Multiple file buffers");
+ case USE_MOUSE:
+ return N_("Mouse support");
+ case NO_CONVERT:
+ return N_("No conversion from DOS/Mac format");
+ case SUSPEND:
+ return N_("Suspension");
+ case SOFTWRAP:
+ return N_("Soft line wrapping");
+ default:
+ return "?????";
+ }
+}
+#endif /* NANO_TINY */
+
+/* Interpret the string given by the rc file and return a
+ shortcut struct, complete with proper value for execute */
+sc *strtosc(int menu, char *input)
+{
+ sc *s;
+
+ s = (sc *)nmalloc(sizeof(sc));
+ s->execute = TRUE; /* overridden as needed below */
+
+
+#ifndef DISABLE_HELP
+ if (!strcasecmp(input, "help"))
+ s->scfunc = do_help_void;
+ else
+#endif
+ if (!strcasecmp(input, "cancel")) {
+ s->scfunc = do_cancel;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "exit"))
+ s->scfunc = do_exit;
+ else if (!strcasecmp(input, "writeout"))
+ s->scfunc = do_writeout_void;
+ else if (!strcasecmp(input, "insert"))
+ s->scfunc = do_insertfile_void;
+ else if (!strcasecmp(input, "whereis"))
+ s->scfunc = do_search;
+ else if (!strcasecmp(input, "up"))
+ s->scfunc = do_up_void;
+ else if (!strcasecmp(input, "down"))
+ s->scfunc = do_down_void;
+ else if (!strcasecmp(input, "pageup")
+ || !strcasecmp(input, "prevpage"))
+ s->scfunc = do_page_up;
+ else if (!strcasecmp(input, "pagedown")
+ || !strcasecmp(input, "nextpage"))
+ s->scfunc = do_page_down;
+ else if (!strcasecmp(input, "cut"))
+ s->scfunc = do_cut_text_void;
+ else if (!strcasecmp(input, "uncut"))
+ s->scfunc = do_uncut_text;
+ else if (!strcasecmp(input, "curpos") ||
+ !strcasecmp(input, "cursorpos"))
+ s->scfunc = do_cursorpos_void;
+ else if (!strcasecmp(input, "firstline"))
+ s->scfunc = do_first_line;
+ else if (!strcasecmp(input, "lastline"))
+ s->scfunc = do_last_line;
+ else if (!strcasecmp(input, "gotoline"))
+ s->scfunc = do_gotolinecolumn_void;
+ else if (!strcasecmp(input, "replace"))
+ s->scfunc = do_replace;
+#ifndef DISABLE_JUSTIFY
+ else if (!strcasecmp(input, "justify"))
+ s->scfunc = do_justify_void;
+ else if (!strcasecmp(input, "beginpara"))
+ s->scfunc = do_para_begin_void;
+ else if (!strcasecmp(input, "endpara"))
+ s->scfunc = do_para_end_void;
+ else if (!strcasecmp(input, "fulljustify"))
+ s->scfunc = do_full_justify;
+#endif
+#ifndef NANO_TINY
+ else if (!strcasecmp(input, "mark"))
+ s->scfunc = do_mark;
+ else if (!strcasecmp(input, "searchagain") ||
+ !strcasecmp(input, "research"))
+ s->scfunc = do_research;
+ else if (!strcasecmp(input, "copytext"))
+ s->scfunc = do_copy_text;
+ else if (!strcasecmp(input, "indent"))
+ s->scfunc = do_indent_void;
+ else if (!strcasecmp(input, "unindent"))
+ s->scfunc = do_unindent;
+ else if (!strcasecmp(input, "scrollup"))
+ s->scfunc = do_scroll_up;
+ else if (!strcasecmp(input, "scrolldown"))
+ s->scfunc = do_scroll_down;
+ else if (!strcasecmp(input, "nextword"))
+ s->scfunc = do_next_word_void;
+ else if (!strcasecmp(input, "suspend"))
+ s->scfunc = do_suspend_void;
+ else if (!strcasecmp(input, "prevword"))
+ s->scfunc = do_prev_word_void;
+ else if (!strcasecmp(input, "findbracket"))
+ s->scfunc = do_find_bracket;
+ else if (!strcasecmp(input, "wordcount"))
+ s->scfunc = do_wordlinechar_count;
+ else if (!strcasecmp(input, "undo"))
+ s->scfunc = do_undo;
+ else if (!strcasecmp(input, "redo"))
+ s->scfunc = do_redo;
+ else if (!strcasecmp(input, "prevhistory")) {
+ s->scfunc = get_history_older_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "nexthistory")) {
+ s->scfunc = get_history_newer_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "nohelp") ||
+ !strcasecmp(input, "nohelp")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = NO_HELP;
+ } else if (!strcasecmp(input, "constupdate")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = CONST_UPDATE;
+ } else if (!strcasecmp(input, "morespace")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = MORE_SPACE;
+ } else if (!strcasecmp(input, "smoothscroll")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = SMOOTH_SCROLL;
+ } else if (!strcasecmp(input, "whitespacedisplay")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = WHITESPACE_DISPLAY;
+ } else if (!strcasecmp(input, "nosyntax")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = NO_COLOR_SYNTAX;
+ } else if (!strcasecmp(input, "smarthome")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = SMART_HOME;
+ } else if (!strcasecmp(input, "autoindent")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = AUTOINDENT;
+ } else if (!strcasecmp(input, "cuttoend")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = CUT_TO_END;
+ } else if (!strcasecmp(input, "nowrap")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = NO_WRAP;
+ } else if (!strcasecmp(input, "tabstospaces")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = TABS_TO_SPACES;
+ } else if (!strcasecmp(input, "backupfile")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = BACKUP_FILE;
+ } else if (!strcasecmp(input, "mutlibuffer")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = MULTIBUFFER;
+ } else if (!strcasecmp(input, "mouse")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = USE_MOUSE;
+ } else if (!strcasecmp(input, "noconvert")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = NO_CONVERT;
+ } else if (!strcasecmp(input, "suspendenable")) {
+ s->scfunc = do_toggle_void;
+ s->execute = FALSE;
+ s->toggle = SUSPEND;
+ }
+#endif /* NANO_TINY */
+ else if (!strcasecmp(input, "right") ||
+ !strcasecmp(input, "forward"))
+ s->scfunc = do_right;
+ else if (!strcasecmp(input, "left") ||
+ !strcasecmp(input, "back"))
+ s->scfunc = do_left;
+ else if (!strcasecmp(input, "up") ||
+ !strcasecmp(input, "prevline"))
+ s->scfunc = do_up_void;
+ else if (!strcasecmp(input, "down") ||
+ !strcasecmp(input, "nextline"))
+ s->scfunc = do_down_void;
+ else if (!strcasecmp(input, "home"))
+ s->scfunc = do_home;
+ else if (!strcasecmp(input, "end"))
+ s->scfunc = do_end;
+#ifdef ENABLE_MULTIBUFFER
+ else if (!strcasecmp(input, "prevbuf"))
+ s->scfunc = switch_to_prev_buffer_void;
+ else if (!strcasecmp(input, "nextbuf"))
+ s->scfunc = switch_to_next_buffer_void;
+#endif
+ else if (!strcasecmp(input, "verbatim"))
+ s->scfunc = do_verbatim_input;
+ else if (!strcasecmp(input, "tab"))
+ s->scfunc = do_tab;
+ else if (!strcasecmp(input, "enter"))
+ s->scfunc = do_enter_void;
+ else if (!strcasecmp(input, "delete"))
+ s->scfunc = do_delete;
+ else if (!strcasecmp(input, "backspace"))
+ s->scfunc = do_backspace;
+ else if (!strcasecmp(input, "refresh"))
+ s->scfunc = total_refresh;
+ else if (!strcasecmp(input, "casesens")) {
+ s->scfunc = case_sens_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "regexp") ||
+ !strcasecmp(input, "regex")) {
+ s->scfunc = regexp_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "dontreplace")) {
+ s->scfunc = no_replace_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "gototext")) {
+ s->scfunc = gototext_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "browser") ||
+ !strcasecmp(input, "tofiles")) {
+ s->scfunc = to_files_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "dosformat")) {
+ s->scfunc = dos_format_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "macformat")) {
+ s->scfunc = mac_format_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "append")) {
+ s->scfunc = append_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "prepend")) {
+ s->scfunc = prepend_void;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "backup")) {
+ s->scfunc = backup_file_void;
+ s->execute = FALSE;
+#ifdef ENABLE_MULTIBUFFER
+ } else if (!strcasecmp(input, "newbuffer")) {
+ s->scfunc = new_buffer_void;
+ s->execute = FALSE;
+#endif
+#ifndef DISABLE_BROWSER
+ } else if (!strcasecmp(input, "firstfile")) {
+ s->scfunc = do_first_file;
+ s->execute = FALSE;
+ } else if (!strcasecmp(input, "lastfile")) {
+ s->scfunc = do_last_file;
+ s->execute = FALSE;
+#endif
+ } else {
+ free(s);
+ return NULL;
+ }
+
+ return s;
+
+}
+
+#ifdef ENABLE_NANORC
+/* Same thing as abnove but for the menu */
+int strtomenu(char *input)
+{
+ if (!strcasecmp(input, "all"))
+ return MALL;
+ else if (!strcasecmp(input, "main"))
+ return MMAIN;
+ else if (!strcasecmp(input, "search"))
+ return MWHEREIS;
+ else if (!strcasecmp(input, "replace"))
+ return MREPLACE;
+ else if (!strcasecmp(input, "replace2") ||
+ !strcasecmp(input, "replacewith"))
+ return MREPLACE2;
+ else if (!strcasecmp(input, "gotoline"))
+ return MGOTOLINE;
+ else if (!strcasecmp(input, "writeout"))
+ return MWRITEFILE;
+ else if (!strcasecmp(input, "insert"))
+ return MINSERTFILE;
+ else if (!strcasecmp(input, "externalcmd") ||
+ !strcasecmp(input, "extcmd"))
+ return MEXTCMD;
+ else if (!strcasecmp(input, "help"))
+ return MHELP;
+ else if (!strcasecmp(input, "spell"))
+ return MSPELL;
+ else if (!strcasecmp(input, "browser"))
+ return MBROWSER;
+ else if (!strcasecmp(input, "whereisfile"))
+ return MWHEREISFILE;
+ else if (!strcasecmp(input, "gotodir"))
+ return MGOTODIR;
+
+ return -1;
+}
+#endif
+
+
+#ifdef DEBUG
+/* This function is used to gracefully return all the memory we've used.
+ * It should be called just before calling exit(). Practically, the
+ * only effect is to cause a segmentation fault if the various data
+ * structures got bolloxed earlier. Thus, we don't bother having this
+ * function unless debugging is turned on. */
+void thanks_for_all_the_fish(void)
+{
+ delwin(topwin);
+ delwin(edit);
+ delwin(bottomwin);
+
+#ifndef DISABLE_JUSTIFY
+ if (quotestr != NULL)
+ free(quotestr);
+#ifdef HAVE_REGEX_H
+ regfree(&quotereg);
+ if (quoteerr != NULL)
+ free(quoteerr);
+#endif
+#endif
+#ifndef NANO_TINY
+ if (backup_dir != NULL)
+ free(backup_dir);
+#endif
+#ifndef DISABLE_OPERATINGDIR
+ if (operating_dir != NULL)
+ free(operating_dir);
+ if (full_operating_dir != NULL)
+ free(full_operating_dir);
+#endif
+ if (last_search != NULL)
+ free(last_search);
+ if (last_replace != NULL)
+ free(last_replace);
+#ifndef DISABLE_SPELLER
+ if (alt_speller != NULL)
+ free(alt_speller);
+#endif
+ if (answer != NULL)
+ free(answer);
+ if (cutbuffer != NULL)
+ free_filestruct(cutbuffer);
+#ifndef DISABLE_JUSTIFY
+ if (jusbuffer != NULL)
+ free_filestruct(jusbuffer);
+#endif
+#ifdef DEBUG
+ /* Free the memory associated with each open file buffer. */
+ if (openfile != NULL)
+ free_openfilestruct(openfile);
+#endif
+#ifdef ENABLE_COLOR
+ if (syntaxstr != NULL)
+ free(syntaxstr);
+ while (syntaxes != NULL) {
+ syntaxtype *bill = syntaxes;
+
+ free(syntaxes->desc);
+ while (syntaxes->extensions != NULL) {
+ exttype *bob = syntaxes->extensions;
+
+ syntaxes->extensions = bob->next;
+ free(bob->ext_regex);
+ if (bob->ext != NULL) {
+ regfree(bob->ext);
+ free(bob->ext);
+ }
+ free(bob);
+ }
+ while (syntaxes->color != NULL) {
+ colortype *bob = syntaxes->color;
+
+ syntaxes->color = bob->next;
+ free(bob->start_regex);
+ if (bob->start != NULL) {
+ regfree(bob->start);
+ free(bob->start);
+ }
+ if (bob->end_regex != NULL)
+ free(bob->end_regex);
+ if (bob->end != NULL) {
+ regfree(bob->end);
+ free(bob->end);
+ }
+ free(bob);
+ }
+ syntaxes = syntaxes->next;
+ free(bill);
+ }
+#endif /* ENABLE_COLOR */
+#ifndef NANO_TINY
+ /* Free the search and replace history lists. */
+ if (searchage != NULL)
+ free_filestruct(searchage);
+ if (replaceage != NULL)
+ free_filestruct(replaceage);
+#endif
+#ifdef ENABLE_NANORC
+ if (homedir != NULL)
+ free(homedir);
+#endif
+}
+
+#endif /* DEBUG */
+
diff --git a/src/help.c b/src/help.c
new file mode 100644
index 0000000..6eee129
--- /dev/null
+++ b/src/help.c
@@ -0,0 +1,563 @@
+/* $Id: help.c 4535 2011-02-26 14:22:37Z astyanax $ */
+/**************************************************************************
+ * help.c *
+ * *
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, *
+ * 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef DISABLE_HELP
+
+static char *help_text = NULL;
+ /* The text displayed in the help window. */
+
+/* Our main help browser function. refresh_func is the function we will
+ * call to refresh the edit window. */
+void do_help(void (*refresh_func)(void))
+{
+ int kbinput = ERR;
+ bool meta_key, func_key, old_no_help = ISSET(NO_HELP);
+ bool abort = FALSE;
+ /* Whether we should abort the help browser. */
+ size_t line = 0;
+ /* The line number in help_text of the first displayed help
+ * line. This variable is zero-based. */
+ size_t last_line = 0;
+ /* The line number in help_text of the last help line. This
+ * variable is zero-based. */
+#ifndef DISABLE_MOUSE
+ /* The current shortcut list. */
+ int oldmenu = currmenu;
+#endif
+ const char *ptr;
+ /* The current line of the help text. */
+ size_t old_line = (size_t)-1;
+ /* The line we were on before the current line. */
+ const sc *s;
+ const subnfunc *f;
+
+ curs_set(0);
+ blank_edit();
+ wattroff(bottomwin, reverse_attr);
+ blank_statusbar();
+
+ /* Set help_text as the string to display. */
+ help_init();
+
+ assert(help_text != NULL);
+
+#ifndef DISABLE_MOUSE
+ /* Set currmenu to allow clicking on the help screen's shortcut
+ * list, after help_init() is called. */
+ currmenu = MHELP;
+#endif
+
+ if (ISSET(NO_HELP)) {
+ /* Make sure that the help screen's shortcut list will actually
+ * be displayed. */
+ UNSET(NO_HELP);
+ window_init();
+ }
+
+ bottombars(MHELP);
+ wnoutrefresh(bottomwin);
+
+ /* Get the last line of the help text. */
+ ptr = help_text;
+
+ for (; *ptr != '\0'; last_line++) {
+ ptr += help_line_len(ptr);
+ if (*ptr == '\n')
+ ptr++;
+ }
+ if (last_line > 0)
+ last_line--;
+
+ while (!abort) {
+ size_t i;
+
+ /* Display the help text if we don't have a key, or if the help
+ * text has moved. */
+ if (kbinput == ERR || line != old_line) {
+ blank_edit();
+
+ ptr = help_text;
+
+ /* Calculate where in the text we should be, based on the
+ * page. */
+ for (i = 0; i < line; i++) {
+ ptr += help_line_len(ptr);
+ if (*ptr == '\n')
+ ptr++;
+ }
+
+ for (i = 0; i < editwinrows && *ptr != '\0'; i++) {
+ size_t j = help_line_len(ptr);
+
+ mvwaddnstr(edit, i, 0, ptr, j);
+ ptr += j;
+ if (*ptr == '\n')
+ ptr++;
+ }
+ }
+
+ wnoutrefresh(edit);
+
+ old_line = line;
+
+ kbinput = get_kbinput(edit, &meta_key, &func_key);
+
+#ifndef DISABLE_MOUSE
+ if (kbinput == KEY_MOUSE) {
+ int mouse_x, mouse_y;
+ get_mouseinput(&mouse_x, &mouse_y, TRUE);
+ continue;
+ /* Redraw the screen. */
+ }
+#endif
+
+ parse_help_input(&kbinput, &meta_key, &func_key);
+ s = get_shortcut(MHELP, &kbinput, &meta_key, &func_key);
+ if (!s)
+ continue;
+ f = sctofunc((sc *) s);
+ if (!f)
+ continue;
+
+ if (f->scfunc == total_refresh) {
+ total_redraw();
+ break;
+ } else if (f->scfunc == do_page_up) {
+ if (line > editwinrows - 2)
+ line -= editwinrows - 2;
+ else
+ line = 0;
+ } else if (f->scfunc == do_page_down) {
+ if (line + (editwinrows - 1) < last_line)
+ line += editwinrows - 2;
+ } else if (f->scfunc == do_up_void) {
+ if (line > 0)
+ line--;
+ } else if (f->scfunc == do_down_void) {
+ if (line + (editwinrows - 1) < last_line)
+ line++;
+ } else if (f->scfunc == do_first_line) {
+ if (meta_key)
+ line = 0;
+ break;
+ } else if (f->scfunc == do_last_line) {
+ if (meta_key) {
+ if (line + (editwinrows - 1) < last_line)
+ line = last_line - (editwinrows - 1);
+ }
+ break;
+ /* Abort the help browser. */
+ } else if (f->scfunc == do_exit) {
+ abort = TRUE;
+ break;
+ }
+ }
+
+#ifndef DISABLE_MOUSE
+ currmenu = oldmenu;
+#endif
+
+ if (old_no_help) {
+ blank_bottombars();
+ wnoutrefresh(bottomwin);
+ SET(NO_HELP);
+ window_init();
+ } else
+ bottombars(currmenu);
+
+ curs_set(1);
+ refresh_func();
+
+ /* The help_init() at the beginning allocated help_text. Since
+ * help_text has now been written to the screen, we don't need it
+ * anymore. */
+ free(help_text);
+ help_text = NULL;
+}
+
+#ifndef DISABLE_BROWSER
+/* Start the help browser for the file browser. */
+void do_browser_help(void)
+{
+ do_help(&browser_refresh);
+}
+#endif
+
+/* This function allocates help_text, and stores the help string in it.
+ * help_text should be NULL initially. */
+void help_init(void)
+{
+ size_t allocsize = 0; /* Space needed for help_text. */
+ const char *htx[3]; /* Untranslated help message. We break
+ * it up into three chunks in case the
+ * full string is too long for the
+ * compiler to handle. */
+ char *ptr;
+ const subnfunc *f;
+ const sc *s;
+ int scsfound = 0;
+
+#ifndef NANO_TINY
+#ifdef ENABLE_NANORC
+ bool old_whitespace = ISSET(WHITESPACE_DISPLAY);
+
+ UNSET(WHITESPACE_DISPLAY);
+#endif
+#endif
+
+ /* First, set up the initial help text for the current function. */
+ if (currmenu == MWHEREIS || currmenu == MREPLACE || currmenu == MREPLACE2) {
+ htx[0] = N_("Search Command Help Text\n\n "
+ "Enter the words or characters you would like to "
+ "search for, and then press Enter. If there is a "
+ "match for the text you entered, the screen will be "
+ "updated to the location of the nearest match for the "
+ "search string.\n\n The previous search string will be "
+ "shown in brackets after the search prompt. Hitting "
+ "Enter without entering any text will perform the "
+ "previous search. ");
+ htx[1] = N_("If you have selected text with the mark and then "
+ "search to replace, only matches in the selected text "
+ "will be replaced.\n\n The following function keys are "
+ "available in Search mode:\n\n");
+ htx[2] = NULL;
+ } else if (currmenu == MGOTOLINE) {
+ htx[0] = N_("Go To Line Help Text\n\n "
+ "Enter the line number that you wish to go to and hit "
+ "Enter. If there are fewer lines of text than the "
+ "number you entered, you will be brought to the last "
+ "line of the file.\n\n The following function keys are "
+ "available in Go To Line mode:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ } else if (currmenu == MINSERTFILE) {
+ htx[0] = N_("Insert File Help Text\n\n "
+ "Type in the name of a file to be inserted into the "
+ "current file buffer at the current cursor "
+ "location.\n\n If you have compiled nano with multiple "
+ "file buffer support, and enable multiple file buffers "
+ "with the -F or --multibuffer command line flags, the "
+ "Meta-F toggle, or a nanorc file, inserting a file "
+ "will cause it to be loaded into a separate buffer "
+ "(use Meta-< and > to switch between file buffers). ");
+ htx[1] = N_("If you need another blank buffer, do not enter "
+ "any filename, or type in a nonexistent filename at "
+ "the prompt and press Enter.\n\n The following "
+ "function keys are available in Insert File mode:\n\n");
+ htx[2] = NULL;
+ } else if (currmenu == MWRITEFILE) {
+ htx[0] = N_("Write File Help Text\n\n "
+ "Type the name that you wish to save the current file "
+ "as and press Enter to save the file.\n\n If you have "
+ "selected text with the mark, you will be prompted to "
+ "save only the selected portion to a separate file. To "
+ "reduce the chance of overwriting the current file with "
+ "just a portion of it, the current filename is not the "
+ "default in this mode.\n\n The following function keys "
+ "are available in Write File mode:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ }
+#ifndef DISABLE_BROWSER
+ else if (currmenu == MBROWSER) {
+ htx[0] = N_("File Browser Help Text\n\n "
+ "The file browser is used to visually browse the "
+ "directory structure to select a file for reading "
+ "or writing. You may use the arrow keys or Page Up/"
+ "Down to browse through the files, and S or Enter to "
+ "choose the selected file or enter the selected "
+ "directory. To move up one level, select the "
+ "directory called \"..\" at the top of the file "
+ "list.\n\n The following function keys are available "
+ "in the file browser:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ } else if (currmenu == MWHEREISFILE) {
+ htx[0] = N_("Browser Search Command Help Text\n\n "
+ "Enter the words or characters you would like to "
+ "search for, and then press Enter. If there is a "
+ "match for the text you entered, the screen will be "
+ "updated to the location of the nearest match for the "
+ "search string.\n\n The previous search string will be "
+ "shown in brackets after the search prompt. Hitting "
+ "Enter without entering any text will perform the "
+ "previous search.\n\n");
+ htx[1] = N_(" The following function keys are available in "
+ "Browser Search mode:\n\n");
+ htx[2] = NULL;
+ } else if (currmenu == MGOTODIR) {
+ htx[0] = N_("Browser Go To Directory Help Text\n\n "
+ "Enter the name of the directory you would like to "
+ "browse to.\n\n If tab completion has not been "
+ "disabled, you can use the Tab key to (attempt to) "
+ "automatically complete the directory name.\n\n The "
+ "following function keys are available in Browser Go "
+ "To Directory mode:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ }
+#endif /* !DISABLE_BROWSER */
+#ifndef DISABLE_SPELLER
+ else if (currmenu == MSPELL) {
+ htx[0] = N_("Spell Check Help Text\n\n "
+ "The spell checker checks the spelling of all text in "
+ "the current file. When an unknown word is "
+ "encountered, it is highlighted and a replacement can "
+ "be edited. It will then prompt to replace every "
+ "instance of the given misspelled word in the current "
+ "file, or, if you have selected text with the mark, in "
+ "the selected text.\n\n The following function keys "
+ "are available in Spell Check mode:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ }
+#endif /* !DISABLE_SPELLER */
+#ifndef NANO_TINY
+ else if (currmenu == MEXTCMD) {
+ htx[0] = N_("Execute Command Help Text\n\n "
+ "This mode allows you to insert the output of a "
+ "command run by the shell into the current buffer (or "
+ "a new buffer in multiple file buffer mode). If you "
+ "need another blank buffer, do not enter any "
+ "command.\n\n The following function keys are "
+ "available in Execute Command mode:\n\n");
+ htx[1] = NULL;
+ htx[2] = NULL;
+ }
+#endif /* !NANO_TINY */
+ else {
+ /* Default to the main help list. */
+ htx[0] = N_("Main nano help text\n\n "
+ "The nano editor is designed to emulate the "
+ "functionality and ease-of-use of the UW Pico text "
+ "editor. There are four main sections of the editor. "
+ "The top line shows the program version, the current "
+ "filename being edited, and whether or not the file "
+ "has been modified. Next is the main editor window "
+ "showing the file being edited. The status line is "
+ "the third line from the bottom and shows important "
+ "messages. ");
+ htx[1] = N_("The bottom two lines show the most commonly used "
+ "shortcuts in the editor.\n\n The notation for "
+ "shortcuts is as follows: Control-key sequences are "
+ "notated with a caret (^) symbol and can be entered "
+ "either by using the Control (Ctrl) key or pressing "
+ "the Escape (Esc) key twice. Escape-key sequences are "
+ "notated with the Meta (M-) symbol and can be entered "
+ "using either the Esc, Alt, or Meta key depending on "
+ "your keyboard setup. ");
+ htx[2] = N_("Also, pressing Esc twice and then typing a "
+ "three-digit decimal number from 000 to 255 will enter "
+ "the character with the corresponding value. The "
+ "following keystrokes are available in the main editor "
+ "window. Alternative keys are shown in "
+ "parentheses:\n\n");
+ }
+
+ htx[0] = _(htx[0]);
+ if (htx[1] != NULL)
+ htx[1] = _(htx[1]);
+ if (htx[2] != NULL)
+ htx[2] = _(htx[2]);
+
+ allocsize += strlen(htx[0]);
+ if (htx[1] != NULL)
+ allocsize += strlen(htx[1]);
+ if (htx[2] != NULL)
+ allocsize += strlen(htx[2]);
+
+ /* Count the shortcut help text. Each entry has up to three keys,
+ * which fill 24 columns, plus translated text, plus one or two
+ * \n's. */
+ for (f = allfuncs; f != NULL; f = f->next)
+ if (f->menus & currmenu)
+ allocsize += (24 * mb_cur_max()) + strlen(f->help) + 2;
+
+#ifndef NANO_TINY
+ /* If we're on the main list, we also count the toggle help text.
+ * Each entry has "M-%c\t\t\t", which fills 24 columns, plus a
+ * space, plus translated text, plus one or two '\n's. */
+ if (currmenu == MMAIN) {
+ size_t endis_len = strlen(_("enable/disable"));
+
+ for (s = sclist; s != NULL; s = s->next)
+ if (s->scfunc == do_toggle_void)
+ allocsize += strlen(_(flagtostr(s->toggle))) + endis_len + 9;
+
+ }
+#endif
+
+ /* help_text has been freed and set to NULL unless the user resized
+ * while in the help screen. */
+ if (help_text != NULL)
+ free(help_text);
+
+ /* Allocate space for the help text. */
+ help_text = charalloc(allocsize + 1);
+
+ /* Now add the text we want. */
+ strcpy(help_text, htx[0]);
+ if (htx[1] != NULL)
+ strcat(help_text, htx[1]);
+ if (htx[2] != NULL)
+ strcat(help_text, htx[2]);
+
+ ptr = help_text + strlen(help_text);
+
+ /* Now add our shortcut info. */
+ for (f = allfuncs; f != NULL; f = f->next) {
+
+ if ((f->menus & currmenu) == 0)
+ continue;
+
+ if (!f->desc || !strcmp(f->desc, ""))
+ continue;
+
+ /* Lets just try and use the first 3 shortcuts
+ from the new struct... */
+ for (s = sclist, scsfound = 0; s != NULL; s = s->next) {
+
+ if (scsfound == 3)
+ continue;
+
+ if (s->type == RAWINPUT)
+ continue;
+
+ if ((s->menu & currmenu) == 0)
+ continue;
+
+ if (s->scfunc == f->scfunc) {
+ scsfound++;
+
+ if (scsfound == 1)
+ ptr += sprintf(ptr, "%s", s->keystr);
+ else
+ ptr += sprintf(ptr, "(%s)", s->keystr);
+ *(ptr++) = '\t';
+ }
+ }
+ /* Pad with tabs if we didnt find 3 */
+ for (; scsfound < 3; scsfound++) {
+ *(ptr++) = '\t';
+ }
+
+ /* The shortcut's help text. */
+ ptr += sprintf(ptr, "%s\n", _(f->help));
+
+ if (f->blank_after)
+ ptr += sprintf(ptr, "\n");
+ }
+
+#ifndef NANO_TINY
+ /* And the toggles... */
+ if (currmenu == MMAIN)
+ for (s = sclist; s != NULL; s = s->next)
+ if (s->scfunc == do_toggle_void)
+ ptr += sprintf(ptr, "(%s)\t\t\t%s %s\n",
+ s->keystr, _(flagtostr(s->toggle)), _("enable/disable"));
+
+
+#ifdef ENABLE_NANORC
+ if (old_whitespace)
+ SET(WHITESPACE_DISPLAY);
+#endif
+#endif
+
+ /* If all went well, we didn't overwrite the allocated space for
+ * help_text. */
+ assert(strlen(help_text) <= allocsize + 1);
+}
+
+/* Determine the shortcut key corresponding to the values of kbinput
+ * (the key itself), meta_key (whether the key is a meta sequence), and
+ * func_key (whether the key is a function key), if any. In the
+ * process, convert certain non-shortcut keys into their corresponding
+ * shortcut keys. */
+void parse_help_input(int *kbinput, bool *meta_key, bool *func_key)
+{
+ get_shortcut(MHELP, kbinput, meta_key, func_key);
+
+ if (!*meta_key) {
+ switch (*kbinput) {
+ /* For consistency with the file browser. */
+ case ' ':
+ *kbinput = sc_seq_or(do_page_up, 0);
+ break;
+ case '-':
+ *kbinput = sc_seq_or(do_page_down, 0);;
+ break;
+ /* Cancel is equivalent to Exit here. */
+ case 'E':
+ case 'e':
+ *kbinput = sc_seq_or(do_exit, 0);;
+ break;
+ }
+ }
+}
+
+/* Calculate the next line of help_text, starting at ptr. */
+size_t help_line_len(const char *ptr)
+{
+ int help_cols = (COLS > 24) ? COLS - 1 : 24;
+
+ /* Try to break the line at (COLS - 1) columns if we have more than
+ * 24 columns, and at 24 columns otherwise. */
+ ssize_t wrap_loc = break_line(ptr, help_cols, TRUE);
+ size_t retval = (wrap_loc < 0) ? 0 : wrap_loc;
+ size_t retval_save = retval;
+
+ /* Get the length of the entire line up to a null or a newline. */
+ while (*(ptr + retval) != '\0' && *(ptr + retval) != '\n')
+ retval += move_mbright(ptr + retval, 0);
+
+ /* If the entire line doesn't go more than one column beyond where
+ * we tried to break it, we should display it as-is. Otherwise, we
+ * should display it only up to the break. */
+ if (strnlenpt(ptr, retval) > help_cols + 1)
+ retval = retval_save;
+
+ return retval;
+}
+
+#endif /* !DISABLE_HELP */
+
+/* Start the help browser for the edit window. */
+void do_help_void(void)
+{
+
+#ifndef DISABLE_HELP
+ /* Start the help browser for the edit window. */
+ do_help(&edit_refresh);
+#else
+ if (currmenu == MMAIN)
+ nano_disabled_msg();
+ else
+ beep();
+#endif
+}
diff --git a/src/move.c b/src/move.c
new file mode 100644
index 0000000..468a02f
--- /dev/null
+++ b/src/move.c
@@ -0,0 +1,670 @@
+/* $Id: move.c 4486 2010-03-21 04:56:37Z astyanax $ */
+/**************************************************************************
+ * move.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <ctype.h>
+
+/* Move to the first line of the file. */
+void do_first_line(void)
+{
+ openfile->current = openfile->edittop = openfile->fileage;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+
+ edit_refresh_needed = 1;
+}
+
+/* Move to the last line of the file. */
+void do_last_line(void)
+{
+ openfile->current = openfile->filebot;
+ openfile->current_x = strlen(openfile->filebot->data);
+ openfile->placewewant = xplustabs();
+ openfile->current_y = editwinrows - 1;
+
+ edit_refresh_needed = 1;
+}
+
+/* Move up one page. */
+void do_page_up(void)
+{
+ int i, skipped = 0;
+
+ /* If there's less than a page of text left on the screen, put the
+ * cursor at the beginning of the first line of the file, and then
+ * update the edit window. */
+ if (openfile->current->lineno == 1 || (!ISSET(SOFTWRAP) &&
+ openfile->current->lineno <= editwinrows - 2)) {
+ do_first_line();
+ return;
+ }
+
+ /* If we're not in smooth scrolling mode, put the cursor at the
+ * beginning of the top line of the edit window, as Pico does. */
+
+#ifndef NANO_TINY
+ if (!ISSET(SMOOTH_SCROLL)) {
+#endif
+ openfile->current = openfile->edittop;
+ openfile->placewewant = openfile->current_y = 0;
+#ifndef NANO_TINY
+ }
+#endif
+
+ for (i = editwinrows - 2; i - skipped > 0 && openfile->current !=
+ openfile->fileage; i--) {
+ openfile->current = openfile->current->prev;
+ if (ISSET(SOFTWRAP) && openfile->current) {
+ skipped += strlenpt(openfile->current->data) / COLS;
+#ifdef DEBUG
+ fprintf(stderr, "do_page_up: i = %d, skipped = %d based on line %ld len %d\n", i, (unsigned long) skipped,
+openfile->current->lineno, strlenpt(openfile->current->data));
+#endif
+ }
+ }
+
+ openfile->current_x = actual_x(openfile->current->data,
+ openfile->placewewant);
+
+#ifdef DEBUG
+ fprintf(stderr, "do_page_up: openfile->current->lineno = %lu, skipped = %d\n", (unsigned long) openfile->current->lineno, skipped);
+#endif
+
+ /* Scroll the edit window up a page. */
+ edit_update(NONE);
+}
+
+/* Move down one page. */
+void do_page_down(void)
+{
+ int i;
+
+ /* If there's less than a page of text left on the screen, put the
+ * cursor at the beginning of the last line of the file, and then
+ * update the edit window. */
+ if (openfile->current->lineno + maxrows - 2 >=
+ openfile->filebot->lineno) {
+ do_last_line();
+ return;
+ }
+
+ /* If we're not in smooth scrolling mode, put the cursor at the
+ * beginning of the top line of the edit window, as Pico does. */
+#ifndef NANO_TINY
+ if (!ISSET(SMOOTH_SCROLL)) {
+#endif
+ openfile->current = openfile->edittop;
+ openfile->placewewant = openfile->current_y = 0;
+#ifndef NANO_TINY
+ }
+#endif
+
+ for (i = maxrows - 2; i > 0 && openfile->current !=
+ openfile->filebot; i--) {
+ openfile->current = openfile->current->next;
+#ifdef DEBUG
+ fprintf(stderr, "do_page_down: moving to line %lu\n", (unsigned long) openfile->current->lineno);
+#endif
+
+ }
+
+ openfile->current_x = actual_x(openfile->current->data,
+ openfile->placewewant);
+
+ /* Scroll the edit window down a page. */
+ edit_update(NONE);
+}
+
+#ifndef DISABLE_JUSTIFY
+/* Move up to the beginning of the last beginning-of-paragraph line
+ * before the current line. If allow_update is TRUE, update the screen
+ * afterwards. */
+void do_para_begin(bool allow_update)
+{
+ filestruct *current_save = openfile->current;
+ const size_t pww_save = openfile->placewewant;
+
+ if (openfile->current != openfile->fileage) {
+ do {
+ openfile->current = openfile->current->prev;
+ openfile->current_y--;
+ } while (!begpar(openfile->current));
+ }
+
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+
+ if (allow_update)
+ edit_redraw(current_save, pww_save);
+}
+
+/* Move up to the beginning of the last beginning-of-paragraph line
+ * before the current line, and update the screen afterwards. */
+void do_para_begin_void(void)
+{
+ do_para_begin(TRUE);
+}
+
+/* Move down to the beginning of the last line of the current paragraph.
+ * Then move down one line farther if there is such a line, or to the
+ * end of the current line if not. If allow_update is TRUE, update the
+ * screen afterwards. A line is the last line of a paragraph if it is
+ * in a paragraph, and the next line either is the beginning line of a
+ * paragraph or isn't in a paragraph. */
+void do_para_end(bool allow_update)
+{
+ filestruct *const current_save = openfile->current;
+ const size_t pww_save = openfile->placewewant;
+
+ while (openfile->current != openfile->filebot &&
+ !inpar(openfile->current))
+ openfile->current = openfile->current->next;
+
+ while (openfile->current != openfile->filebot &&
+ inpar(openfile->current->next) &&
+ !begpar(openfile->current->next)) {
+ openfile->current = openfile->current->next;
+ openfile->current_y++;
+ }
+
+ if (openfile->current != openfile->filebot) {
+ openfile->current = openfile->current->next;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+ } else {
+ openfile->current_x = strlen(openfile->current->data);
+ openfile->placewewant = xplustabs();
+ }
+
+ if (allow_update)
+ edit_redraw(current_save, pww_save);
+}
+
+/* Move down to the beginning of the last line of the current paragraph.
+ * Then move down one line farther if there is such a line, or to the
+ * end of the current line if not, and update the screen afterwards. */
+void do_para_end_void(void)
+{
+ do_para_end(TRUE);
+}
+#endif /* !DISABLE_JUSTIFY */
+
+#ifndef NANO_TINY
+/* Move to the next word in the file. If allow_punct is TRUE, treat
+ * punctuation as part of a word. If allow_update is TRUE, update the
+ * screen afterwards. Return TRUE if we started on a word, and FALSE
+ * otherwise. */
+bool do_next_word(bool allow_punct, bool allow_update)
+{
+ size_t pww_save = openfile->placewewant;
+ filestruct *current_save = openfile->current;
+ char *char_mb;
+ int char_mb_len;
+ bool end_line = FALSE, started_on_word = FALSE;
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ char_mb = charalloc(mb_cur_max());
+
+ /* Move forward until we find the character after the last letter of
+ * the current word. */
+ while (!end_line) {
+ char_mb_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, char_mb, NULL);
+
+ /* If we've found it, stop moving forward through the current
+ * line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ /* If we haven't found it, then we've started on a word, so set
+ * started_on_word to TRUE. */
+ started_on_word = TRUE;
+
+ if (openfile->current->data[openfile->current_x] == '\0')
+ end_line = TRUE;
+ else
+ openfile->current_x += char_mb_len;
+ }
+
+ /* Move forward until we find the first letter of the next word. */
+ if (openfile->current->data[openfile->current_x] == '\0')
+ end_line = TRUE;
+ else
+ openfile->current_x += char_mb_len;
+
+ for (; openfile->current != NULL;
+ openfile->current = openfile->current->next) {
+ while (!end_line) {
+ char_mb_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, char_mb, NULL);
+
+ /* If we've found it, stop moving forward through the
+ * current line. */
+ if (is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (openfile->current->data[openfile->current_x] == '\0')
+ end_line = TRUE;
+ else
+ openfile->current_x += char_mb_len;
+ }
+
+ /* If we've found it, stop moving forward to the beginnings of
+ * subsequent lines. */
+ if (!end_line)
+ break;
+
+ if (openfile->current != openfile->filebot) {
+ end_line = FALSE;
+ openfile->current_x = 0;
+ }
+ }
+
+ free(char_mb);
+
+ /* If we haven't found it, move to the end of the file. */
+ if (openfile->current == NULL)
+ openfile->current = openfile->filebot;
+
+ openfile->placewewant = xplustabs();
+
+ /* If allow_update is TRUE, update the screen. */
+ if (allow_update)
+ edit_redraw(current_save, pww_save);
+
+ /* Return whether we started on a word. */
+ return started_on_word;
+}
+
+/* Move to the next word in the file, treating punctuation as part of a
+ * word if the WORD_BOUNDS flag is set, and update the screen
+ * afterwards. */
+void do_next_word_void(void)
+{
+ do_next_word(ISSET(WORD_BOUNDS), TRUE);
+}
+
+/* Move to the previous word in the file. If allow_punct is TRUE, treat
+ * punctuation as part of a word. If allow_update is TRUE, update the
+ * screen afterwards. Return TRUE if we started on a word, and FALSE
+ * otherwise. */
+bool do_prev_word(bool allow_punct, bool allow_update)
+{
+ size_t pww_save = openfile->placewewant;
+ filestruct *current_save = openfile->current;
+ char *char_mb;
+ int char_mb_len;
+ bool begin_line = FALSE, started_on_word = FALSE;
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ char_mb = charalloc(mb_cur_max());
+
+ /* Move backward until we find the character before the first letter
+ * of the current word. */
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, char_mb, NULL);
+
+ /* If we've found it, stop moving backward through the current
+ * line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ /* If we haven't found it, then we've started on a word, so set
+ * started_on_word to TRUE. */
+ started_on_word = TRUE;
+
+ if (openfile->current_x == 0)
+ begin_line = TRUE;
+ else
+ openfile->current_x = move_mbleft(openfile->current->data,
+ openfile->current_x);
+ }
+
+ /* Move backward until we find the last letter of the previous
+ * word. */
+ if (openfile->current_x == 0)
+ begin_line = TRUE;
+ else
+ openfile->current_x = move_mbleft(openfile->current->data,
+ openfile->current_x);
+
+ for (; openfile->current != NULL;
+ openfile->current = openfile->current->prev) {
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, char_mb, NULL);
+
+ /* If we've found it, stop moving backward through the
+ * current line. */
+ if (is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (openfile->current_x == 0)
+ begin_line = TRUE;
+ else
+ openfile->current_x =
+ move_mbleft(openfile->current->data,
+ openfile->current_x);
+ }
+
+ /* If we've found it, stop moving backward to the ends of
+ * previous lines. */
+ if (!begin_line)
+ break;
+
+ if (openfile->current != openfile->fileage) {
+ begin_line = FALSE;
+ openfile->current_x = strlen(openfile->current->prev->data);
+ }
+ }
+
+ /* If we haven't found it, move to the beginning of the file. */
+ if (openfile->current == NULL)
+ openfile->current = openfile->fileage;
+ /* If we've found it, move backward until we find the character
+ * before the first letter of the previous word. */
+ else if (!begin_line) {
+ if (openfile->current_x == 0)
+ begin_line = TRUE;
+ else
+ openfile->current_x = move_mbleft(openfile->current->data,
+ openfile->current_x);
+
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, char_mb, NULL);
+
+ /* If we've found it, stop moving backward through the
+ * current line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (openfile->current_x == 0)
+ begin_line = TRUE;
+ else
+ openfile->current_x =
+ move_mbleft(openfile->current->data,
+ openfile->current_x);
+ }
+
+ /* If we've found it, move forward to the first letter of the
+ * previous word. */
+ if (!begin_line)
+ openfile->current_x += char_mb_len;
+ }
+
+ free(char_mb);
+
+ openfile->placewewant = xplustabs();
+
+ /* If allow_update is TRUE, update the screen. */
+ if (allow_update)
+ edit_redraw(current_save, pww_save);
+
+ /* Return whether we started on a word. */
+ return started_on_word;
+}
+
+/* Move to the previous word in the file, treating punctuation as part
+ * of a word if the WORD_BOUNDS flag is set, and update the screen
+ * afterwards. */
+void do_prev_word_void(void)
+{
+ do_prev_word(ISSET(WORD_BOUNDS), TRUE);
+}
+#endif /* !NANO_TINY */
+
+/* Move to the beginning of the current line. If the SMART_HOME flag is
+ * set, move to the first non-whitespace character of the current line
+ * if we aren't already there, or to the beginning of the current line
+ * if we are. */
+void do_home(void)
+{
+ size_t pww_save = openfile->placewewant;
+
+#ifndef NANO_TINY
+ if (ISSET(SMART_HOME)) {
+ size_t current_x_save = openfile->current_x;
+
+ openfile->current_x = indent_length(openfile->current->data);
+
+ if (openfile->current_x == current_x_save ||
+ openfile->current->data[openfile->current_x] == '\0')
+ openfile->current_x = 0;
+
+ openfile->placewewant = xplustabs();
+ } else {
+#endif
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+#ifndef NANO_TINY
+ }
+#endif
+
+ if (need_horizontal_update(pww_save))
+ update_line(openfile->current, openfile->current_x);
+}
+
+/* Move to the end of the current line. */
+void do_end(void)
+{
+ size_t pww_save = openfile->placewewant;
+
+ openfile->current_x = strlen(openfile->current->data);
+ openfile->placewewant = xplustabs();
+
+ if (need_horizontal_update(pww_save))
+ update_line(openfile->current, openfile->current_x);
+}
+
+/* If scroll_only is FALSE, move up one line. If scroll_only is TRUE,
+ * scroll up one line without scrolling the cursor. */
+void do_up(
+#ifndef NANO_TINY
+ bool scroll_only
+#else
+ void
+#endif
+ )
+{
+ /* If we're at the top of the file, or if scroll_only is TRUE and
+ * the top of the file is onscreen, get out. */
+ if (openfile->current == openfile->fileage
+#ifndef NANO_TINY
+ || (scroll_only && openfile->edittop == openfile->fileage)
+#endif
+ )
+ return;
+
+ assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno);
+
+ /* Move the current line of the edit window up. */
+ openfile->current = openfile->current->prev;
+ openfile->current_x = actual_x(openfile->current->data,
+ openfile->placewewant);
+
+ /* If scroll_only is FALSE and if we're on the first line of the
+ * edit window, scroll the edit window up one line if we're in
+ * smooth scrolling mode, or up half a page if we're not. If
+ * scroll_only is TRUE, scroll the edit window up one line
+ * unconditionally. */
+ if (openfile->current_y == 0 || (ISSET(SOFTWRAP) && openfile->edittop->lineno == openfile->current->next->lineno)
+#ifndef NANO_TINY
+ || scroll_only
+#endif
+ )
+ edit_scroll(UP_DIR,
+#ifndef NANO_TINY
+ (ISSET(SMOOTH_SCROLL) || scroll_only) ? 1 :
+#endif
+ editwinrows / 2 + 1);
+
+ /* If we're below the first line of the edit window, update the
+ * line we were on before and the line we're on now. The former
+ * needs to be redrawn if we're not on the first page, and the
+ * latter needs to be drawn unconditionally. */
+ if (openfile->current_y > 0) {
+ if (need_vertical_update(0))
+ update_line(openfile->current->next, 0);
+ update_line(openfile->current, openfile->current_x);
+ }
+}
+
+/* Move up one line. */
+void do_up_void(void)
+{
+ do_up(
+#ifndef NANO_TINY
+ FALSE
+#endif
+ );
+}
+
+#ifndef NANO_TINY
+/* Scroll up one line without scrolling the cursor. */
+void do_scroll_up(void)
+{
+ do_up(TRUE);
+}
+#endif
+
+/* If scroll_only is FALSE, move down one line. If scroll_only is TRUE,
+ * scroll down one line without scrolling the cursor. */
+void do_down(
+#ifndef NANO_TINY
+ bool scroll_only
+#else
+ void
+#endif
+ )
+{
+ bool onlastline = FALSE;
+
+ /* If we're at the bottom of the file, get out. */
+ if (openfile->current == openfile->filebot)
+ return;
+
+
+ assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno);
+
+ /* Move the current line of the edit window down. */
+ openfile->current = openfile->current->next;
+ openfile->current_x = actual_x(openfile->current->data,
+ openfile->placewewant);
+
+ if (ISSET(SOFTWRAP)) {
+ if (openfile->current->lineno - openfile->edittop->lineno >= maxrows)
+ onlastline = TRUE;
+ }
+
+ /* If scroll_only is FALSE and if we're on the first line of the
+ * edit window, scroll the edit window down one line if we're in
+ * smooth scrolling mode, or down half a page if we're not. If
+ * scroll_only is TRUE, scroll the edit window down one line
+ * unconditionally. */
+ if (onlastline || openfile->current_y == editwinrows - 1
+#ifndef NANO_TINY
+ || scroll_only
+#endif
+ ) {
+ edit_scroll(DOWN_DIR,
+#ifndef NANO_TINY
+ (ISSET(SMOOTH_SCROLL) || scroll_only) ? 1 :
+#endif
+ editwinrows / 2 + 1);
+
+ edit_refresh_needed = TRUE;
+ }
+ /* If we're above the last line of the edit window, update the line
+ * we were on before and the line we're on now. The former needs to
+ * be redrawn if we're not on the first page, and the latter needs
+ * to be drawn unconditionally. */
+ if (ISSET(SOFTWRAP) || openfile->current_y < editwinrows - 1) {
+ if (need_vertical_update(0))
+ update_line(openfile->current->prev, 0);
+ update_line(openfile->current, openfile->current_x);
+ }
+}
+
+/* Move down one line. */
+void do_down_void(void)
+{
+ do_down(
+#ifndef NANO_TINY
+ FALSE
+#endif
+ );
+}
+
+#ifndef NANO_TINY
+/* Scroll down one line without scrolling the cursor. */
+void do_scroll_down(void)
+{
+ do_down(TRUE);
+}
+#endif
+
+/* Move left one character. */
+void do_left(void)
+{
+ size_t pww_save = openfile->placewewant;
+
+ if (openfile->current_x > 0)
+ openfile->current_x = move_mbleft(openfile->current->data,
+ openfile->current_x);
+ else if (openfile->current != openfile->fileage) {
+ do_up_void();
+ openfile->current_x = strlen(openfile->current->data);
+ }
+
+ openfile->placewewant = xplustabs();
+
+ if (need_horizontal_update(pww_save))
+ update_line(openfile->current, openfile->current_x);
+}
+
+/* Move right one character. */
+void do_right(void)
+{
+ size_t pww_save = openfile->placewewant;
+
+ assert(openfile->current_x <= strlen(openfile->current->data));
+
+ if (openfile->current->data[openfile->current_x] != '\0')
+ openfile->current_x = move_mbright(openfile->current->data,
+ openfile->current_x);
+ else if (openfile->current != openfile->filebot) {
+ do_down_void();
+ openfile->current_x = 0;
+ }
+
+ openfile->placewewant = xplustabs();
+
+ if (need_horizontal_update(pww_save))
+ update_line(openfile->current, openfile->current_x);
+}
diff --git a/src/nano.c b/src/nano.c
new file mode 100644
index 0000000..269ab29
--- /dev/null
+++ b/src/nano.c
@@ -0,0 +1,2735 @@
+/* $Id: nano.c 4530 2011-02-18 07:30:57Z astyanax $ */
+/**************************************************************************
+ * nano.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <locale.h>
+#include <time.h>
+#ifdef ENABLE_UTF8
+#include <langinfo.h>
+#endif
+#include <termios.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifndef NANO_TINY
+#include <sys/ioctl.h>
+#endif
+
+#ifndef DISABLE_MOUSE
+static int oldinterval = -1;
+ /* Used to store the user's original mouse click interval. */
+#endif
+#ifdef ENABLE_NANORC
+static bool no_rcfiles = FALSE;
+ /* Should we ignore all rcfiles? */
+#endif
+static struct termios oldterm;
+ /* The user's original terminal settings. */
+static struct sigaction act;
+ /* Used to set up all our fun signal handlers. */
+
+/* Create a new filestruct node. Note that we do not set prevnode->next
+ * to the new line. */
+filestruct *make_new_node(filestruct *prevnode)
+{
+ filestruct *newnode = (filestruct *)nmalloc(sizeof(filestruct));
+
+ newnode->data = NULL;
+ newnode->prev = prevnode;
+ newnode->next = NULL;
+ newnode->lineno = (prevnode != NULL) ? prevnode->lineno + 1 : 1;
+
+#ifdef ENABLE_COLOR
+ newnode->multidata = NULL;
+#endif
+
+ return newnode;
+}
+
+/* Make a copy of a filestruct node. */
+filestruct *copy_node(const filestruct *src)
+{
+ filestruct *dst;
+
+ assert(src != NULL);
+
+ dst = (filestruct *)nmalloc(sizeof(filestruct));
+
+ dst->data = mallocstrcpy(NULL, src->data);
+ dst->next = src->next;
+ dst->prev = src->prev;
+ dst->lineno = src->lineno;
+#ifdef ENABLE_COLOR
+ dst->multidata = NULL;
+#endif
+
+ return dst;
+}
+
+/* Splice a node into an existing filestruct. */
+void splice_node(filestruct *begin, filestruct *newnode, filestruct
+ *end)
+{
+ assert(newnode != NULL && begin != NULL);
+
+ newnode->next = end;
+ newnode->prev = begin;
+ begin->next = newnode;
+ if (end != NULL)
+ end->prev = newnode;
+}
+
+/* Unlink a node from the rest of the filestruct. */
+void unlink_node(const filestruct *fileptr)
+{
+ assert(fileptr != NULL);
+
+ if (fileptr->prev != NULL)
+ fileptr->prev->next = fileptr->next;
+ if (fileptr->next != NULL)
+ fileptr->next->prev = fileptr->prev;
+}
+
+/* Delete a node from the filestruct. */
+void delete_node(filestruct *fileptr)
+{
+ assert(fileptr != NULL && fileptr->data != NULL);
+
+ if (fileptr->data != NULL)
+ free(fileptr->data);
+
+#ifdef ENABLE_COLOR
+ if (fileptr->multidata)
+ free(fileptr->multidata);
+#endif
+
+ free(fileptr);
+}
+
+/* Duplicate a whole filestruct. */
+filestruct *copy_filestruct(const filestruct *src)
+{
+ filestruct *head, *copy;
+
+ assert(src != NULL);
+
+ copy = copy_node(src);
+ copy->prev = NULL;
+ head = copy;
+ src = src->next;
+
+ while (src != NULL) {
+ copy->next = copy_node(src);
+ copy->next->prev = copy;
+ copy = copy->next;
+
+ src = src->next;
+ }
+
+ copy->next = NULL;
+
+ return head;
+}
+
+/* Free a filestruct. */
+void free_filestruct(filestruct *src)
+{
+ assert(src != NULL);
+
+ while (src->next != NULL) {
+ src = src->next;
+ delete_node(src->prev);
+ }
+
+ delete_node(src);
+}
+
+/* Renumber all entries in a filestruct, starting with fileptr. */
+void renumber(filestruct *fileptr)
+{
+ ssize_t line;
+
+ assert(fileptr != NULL);
+
+ line = (fileptr->prev == NULL) ? 0 : fileptr->prev->lineno;
+
+ assert(fileptr != fileptr->next);
+
+ for (; fileptr != NULL; fileptr = fileptr->next)
+ fileptr->lineno = ++line;
+}
+
+/* Partition a filestruct so that it begins at (top, top_x) and ends at
+ * (bot, bot_x). */
+partition *partition_filestruct(filestruct *top, size_t top_x,
+ filestruct *bot, size_t bot_x)
+{
+ partition *p;
+
+ assert(top != NULL && bot != NULL && openfile->fileage != NULL && openfile->filebot != NULL);
+
+ /* Initialize the partition. */
+ p = (partition *)nmalloc(sizeof(partition));
+
+ /* If the top and bottom of the partition are different from the top
+ * and bottom of the filestruct, save the latter and then set them
+ * to top and bot. */
+ if (top != openfile->fileage) {
+ p->fileage = openfile->fileage;
+ openfile->fileage = top;
+ } else
+ p->fileage = NULL;
+ if (bot != openfile->filebot) {
+ p->filebot = openfile->filebot;
+ openfile->filebot = bot;
+ } else
+ p->filebot = NULL;
+
+ /* Save the line above the top of the partition, detach the top of
+ * the partition from it, and save the text before top_x in
+ * top_data. */
+ p->top_prev = top->prev;
+ top->prev = NULL;
+ p->top_data = mallocstrncpy(NULL, top->data, top_x + 1);
+ p->top_data[top_x] = '\0';
+
+ /* Save the line below the bottom of the partition, detach the
+ * bottom of the partition from it, and save the text after bot_x in
+ * bot_data. */
+ p->bot_next = bot->next;
+ bot->next = NULL;
+ p->bot_data = mallocstrcpy(NULL, bot->data + bot_x);
+
+ /* Remove all text after bot_x at the bottom of the partition. */
+ null_at(&bot->data, bot_x);
+
+ /* Remove all text before top_x at the top of the partition. */
+ charmove(top->data, top->data + top_x, strlen(top->data) -
+ top_x + 1);
+ align(&top->data);
+
+ /* Return the partition. */
+ return p;
+}
+
+/* Unpartition a filestruct so that it begins at (fileage, 0) and ends
+ * at (filebot, strlen(filebot->data)) again. */
+void unpartition_filestruct(partition **p)
+{
+ char *tmp;
+
+ assert(p != NULL && openfile->fileage != NULL && openfile->filebot != NULL);
+
+ /* Reattach the line above the top of the partition, and restore the
+ * text before top_x from top_data. Free top_data when we're done
+ * with it. */
+ tmp = mallocstrcpy(NULL, openfile->fileage->data);
+ openfile->fileage->prev = (*p)->top_prev;
+ if (openfile->fileage->prev != NULL)
+ openfile->fileage->prev->next = openfile->fileage;
+ openfile->fileage->data = charealloc(openfile->fileage->data,
+ strlen((*p)->top_data) + strlen(openfile->fileage->data) + 1);
+ strcpy(openfile->fileage->data, (*p)->top_data);
+ free((*p)->top_data);
+ strcat(openfile->fileage->data, tmp);
+ free(tmp);
+
+ /* Reattach the line below the bottom of the partition, and restore
+ * the text after bot_x from bot_data. Free bot_data when we're
+ * done with it. */
+ openfile->filebot->next = (*p)->bot_next;
+ if (openfile->filebot->next != NULL)
+ openfile->filebot->next->prev = openfile->filebot;
+ openfile->filebot->data = charealloc(openfile->filebot->data,
+ strlen(openfile->filebot->data) + strlen((*p)->bot_data) + 1);
+ strcat(openfile->filebot->data, (*p)->bot_data);
+ free((*p)->bot_data);
+
+ /* Restore the top and bottom of the filestruct, if they were
+ * different from the top and bottom of the partition. */
+ if ((*p)->fileage != NULL)
+ openfile->fileage = (*p)->fileage;
+ if ((*p)->filebot != NULL)
+ openfile->filebot = (*p)->filebot;
+
+ /* Uninitialize the partition. */
+ free(*p);
+ *p = NULL;
+}
+
+/* Move all the text between (top, top_x) and (bot, bot_x) in the
+ * current filestruct to a filestruct beginning with file_top and ending
+ * with file_bot. If no text is between (top, top_x) and (bot, bot_x),
+ * don't do anything. */
+void move_to_filestruct(filestruct **file_top, filestruct **file_bot,
+ filestruct *top, size_t top_x, filestruct *bot, size_t bot_x)
+{
+ filestruct *top_save;
+ bool edittop_inside;
+#ifndef NANO_TINY
+ bool mark_inside = FALSE;
+#endif
+
+ assert(file_top != NULL && file_bot != NULL && top != NULL && bot != NULL);
+
+ /* If (top, top_x)-(bot, bot_x) doesn't cover any text, get out. */
+ if (top == bot && top_x == bot_x)
+ return;
+
+ /* Partition the filestruct so that it contains only the text from
+ * (top, top_x) to (bot, bot_x), keep track of whether the top of
+ * the edit window is inside the partition, and keep track of
+ * whether the mark begins inside the partition. */
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+ edittop_inside = (openfile->edittop->lineno >=
+ openfile->fileage->lineno && openfile->edittop->lineno <=
+ openfile->filebot->lineno);
+#ifndef NANO_TINY
+ if (openfile->mark_set)
+ mark_inside = (openfile->mark_begin->lineno >=
+ openfile->fileage->lineno &&
+ openfile->mark_begin->lineno <=
+ openfile->filebot->lineno &&
+ (openfile->mark_begin != openfile->fileage ||
+ openfile->mark_begin_x >= top_x) &&
+ (openfile->mark_begin != openfile->filebot ||
+ openfile->mark_begin_x <= bot_x));
+#endif
+
+ /* Get the number of characters in the text, and subtract it from
+ * totsize. */
+ openfile->totsize -= get_totsize(top, bot);
+
+ if (*file_top == NULL) {
+ /* If file_top is empty, just move all the text directly into
+ * it. This is equivalent to tacking the text in top onto the
+ * (lack of) text at the end of file_top. */
+ *file_top = openfile->fileage;
+ *file_bot = openfile->filebot;
+
+ /* Renumber starting with file_top. */
+ renumber(*file_top);
+ } else {
+ filestruct *file_bot_save = *file_bot;
+
+ /* Otherwise, tack the text in top onto the text at the end of
+ * file_bot. */
+ (*file_bot)->data = charealloc((*file_bot)->data,
+ strlen((*file_bot)->data) +
+ strlen(openfile->fileage->data) + 1);
+ strcat((*file_bot)->data, openfile->fileage->data);
+
+ /* Attach the line after top to the line after file_bot. Then,
+ * if there's more than one line after top, move file_bot down
+ * to bot. */
+ (*file_bot)->next = openfile->fileage->next;
+ if ((*file_bot)->next != NULL) {
+ (*file_bot)->next->prev = *file_bot;
+ *file_bot = openfile->filebot;
+ }
+
+ /* Renumber starting with the line after the original
+ * file_bot. */
+ if (file_bot_save->next != NULL)
+ renumber(file_bot_save->next);
+ }
+
+ /* Since the text has now been saved, remove it from the
+ * filestruct. */
+ openfile->fileage = (filestruct *)nmalloc(sizeof(filestruct));
+ openfile->fileage->data = mallocstrcpy(NULL, "");
+ openfile->filebot = openfile->fileage;
+
+#ifdef ENABLE_COLOR
+ openfile->fileage->multidata = NULL;
+#endif
+
+ /* Restore the current line and cursor position. If the mark begins
+ * inside the partition, set the beginning of the mark to where the
+ * saved text used to start. */
+ openfile->current = openfile->fileage;
+ openfile->current_x = top_x;
+#ifndef NANO_TINY
+ if (mark_inside) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x = openfile->current_x;
+ }
+#endif
+
+ top_save = openfile->fileage;
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again, minus the saved text. */
+ unpartition_filestruct(&filepart);
+
+ /* If the top of the edit window was inside the old partition, put
+ * it in range of current. */
+ if (edittop_inside)
+ edit_update(NONE);
+
+ /* Renumber starting with the beginning line of the old
+ * partition. */
+ renumber(top_save);
+
+ /* If the NO_NEWLINES flag isn't set, and the text doesn't end with
+ * a magicline, add a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
+ new_magicline();
+}
+
+/* Copy all the text from the filestruct beginning with file_top and
+ * ending with file_bot to the current filestruct at the current cursor
+ * position. */
+void copy_from_filestruct(filestruct *file_top, filestruct *file_bot)
+{
+ filestruct *top_save;
+ size_t current_x_save = openfile->current_x;
+ bool edittop_inside;
+#ifndef NANO_TINY
+ bool right_side_up = FALSE, single_line = FALSE;
+#endif
+
+ assert(file_top != NULL && file_bot != NULL);
+
+#ifndef NANO_TINY
+ /* Keep track of whether the mark begins inside the partition and
+ * will need adjustment. */
+ if (openfile->mark_set) {
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, &right_side_up);
+
+ single_line = (top == bot);
+ }
+#endif
+
+ /* Partition the filestruct so that it contains no text, and keep
+ * track of whether the top of the edit window is inside the
+ * partition. */
+ filepart = partition_filestruct(openfile->current,
+ openfile->current_x, openfile->current, openfile->current_x);
+ edittop_inside = (openfile->edittop == openfile->fileage);
+
+ /* Put the top and bottom of the filestruct at copies of file_top
+ * and file_bot. */
+ openfile->fileage = copy_filestruct(file_top);
+ openfile->filebot = openfile->fileage;
+ while (openfile->filebot->next != NULL)
+ openfile->filebot = openfile->filebot->next;
+
+ /* Restore the current line and cursor position. If the mark begins
+ * inside the partition, adjust the mark coordinates to compensate
+ * for the change in the current line. */
+ openfile->current = openfile->filebot;
+ openfile->current_x = strlen(openfile->filebot->data);
+ if (openfile->fileage == openfile->filebot) {
+#ifndef NANO_TINY
+ if (openfile->mark_set) {
+ openfile->mark_begin = openfile->current;
+ if (!right_side_up)
+ openfile->mark_begin_x += openfile->current_x;
+ }
+#endif
+ openfile->current_x += current_x_save;
+ }
+#ifndef NANO_TINY
+ else if (openfile->mark_set) {
+ if (!right_side_up) {
+ if (single_line) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x -= current_x_save;
+ } else
+ openfile->mark_begin_x -= openfile->current_x;
+ }
+ }
+#endif
+
+ /* Get the number of characters in the copied text, and add it to
+ * totsize. */
+ openfile->totsize += get_totsize(openfile->fileage,
+ openfile->filebot);
+
+ /* Update the current y-coordinate to account for the number of
+ * lines the copied text has, less one since the first line will be
+ * tacked onto the current line. */
+ openfile->current_y += openfile->filebot->lineno - 1;
+
+ top_save = openfile->fileage;
+
+ /* If the top of the edit window is inside the partition, set it to
+ * where the copied text now starts. */
+ if (edittop_inside)
+ openfile->edittop = openfile->fileage;
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again, plus the copied text. */
+ unpartition_filestruct(&filepart);
+
+ /* Renumber starting with the beginning line of the old
+ * partition. */
+ renumber(top_save);
+
+ /* If the NO_NEWLINES flag isn't set, and the text doesn't end with
+ * a magicline, add a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
+ new_magicline();
+}
+
+/* Create a new openfilestruct node. */
+openfilestruct *make_new_opennode(void)
+{
+ openfilestruct *newnode =
+ (openfilestruct *)nmalloc(sizeof(openfilestruct));
+
+ newnode->filename = NULL;
+ newnode->fileage = NULL;
+ newnode->filebot = NULL;
+ newnode->edittop = NULL;
+ newnode->current = NULL;
+#ifndef NANO_TINY
+ newnode->current_stat = NULL;
+ newnode->last_action = OTHER;
+#endif
+
+ return newnode;
+}
+
+/* Splice a node into an existing openfilestruct. */
+void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
+ openfilestruct *end)
+{
+ assert(newnode != NULL && begin != NULL);
+
+ newnode->next = end;
+ newnode->prev = begin;
+ begin->next = newnode;
+
+ if (end != NULL)
+ end->prev = newnode;
+}
+
+/* Unlink a node from the rest of the openfilestruct, and delete it. */
+void unlink_opennode(openfilestruct *fileptr)
+{
+ assert(fileptr != NULL && fileptr->prev != NULL && fileptr->next != NULL && fileptr != fileptr->prev && fileptr != fileptr->next);
+
+ fileptr->prev->next = fileptr->next;
+ fileptr->next->prev = fileptr->prev;
+
+ delete_opennode(fileptr);
+}
+
+/* Delete a node from the openfilestruct. */
+void delete_opennode(openfilestruct *fileptr)
+{
+ assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
+
+ free(fileptr->filename);
+ free_filestruct(fileptr->fileage);
+#ifndef NANO_TINY
+ if (fileptr->current_stat != NULL)
+ free(fileptr->current_stat);
+#endif
+
+ free(fileptr);
+}
+
+#ifdef DEBUG
+/* Deallocate all memory associated with this and later files, including
+ * the lines of text. */
+void free_openfilestruct(openfilestruct *src)
+{
+ assert(src != NULL);
+
+ while (src != src->next) {
+ src = src->next;
+ delete_opennode(src->prev);
+ }
+
+ delete_opennode(src);
+}
+#endif
+
+/* Display a warning about a key disabled in view mode. */
+void print_view_warning(void)
+{
+ statusbar(_("Key invalid in view mode"));
+}
+
+/* Make nano exit gracefully. */
+void finish(void)
+{
+ /* Blank the statusbar (and shortcut list, if applicable), and move
+ * the cursor to the last line of the screen. */
+ if (!ISSET(NO_HELP))
+ blank_bottombars();
+ else
+ blank_statusbar();
+ wrefresh(bottomwin);
+ endwin();
+
+ /* Restore the old terminal settings. */
+ tcsetattr(0, TCSANOW, &oldterm);
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ if (!no_rcfiles && ISSET(HISTORYLOG))
+ save_history();
+ if (!no_rcfiles && ISSET(POS_HISTORY)) {
+ update_poshistory(openfile->filename, openfile->current->lineno, xplustabs()+1);
+ save_poshistory();
+ }
+#endif
+
+#ifdef DEBUG
+ thanks_for_all_the_fish();
+#endif
+
+ /* Get out. */
+ exit(0);
+}
+
+/* Make nano die gracefully. */
+void die(const char *msg, ...)
+{
+ va_list ap;
+
+ endwin();
+
+ /* Restore the old terminal settings. */
+ tcsetattr(0, TCSANOW, &oldterm);
+
+ va_start(ap, msg);
+ vfprintf(stderr, msg, ap);
+ va_end(ap);
+
+ /* Save the current file buffer if it's been modified. */
+ if (openfile && openfile->modified) {
+ /* If we've partitioned the filestruct, unpartition it now. */
+ if (filepart != NULL)
+ unpartition_filestruct(&filepart);
+
+ die_save_file(openfile->filename
+#ifndef NANO_TINY
+ , openfile->current_stat
+#endif
+ );
+ }
+
+#ifdef ENABLE_MULTIBUFFER
+ /* Save all of the other modified file buffers, if any. */
+ if (openfile != NULL) {
+ openfilestruct *tmp = openfile;
+
+ while (tmp != openfile->next) {
+ openfile = openfile->next;
+
+ /* Save the current file buffer if it's been modified. */
+ if (openfile->modified)
+ die_save_file(openfile->filename
+#ifndef NANO_TINY
+ , openfile->current_stat
+#endif
+ );
+ }
+ }
+#endif
+
+ /* Get out. */
+ exit(1);
+}
+
+/* Save the current file under the name spacified in die_filename, which
+ * is modified to be unique if necessary. */
+void die_save_file(const char *die_filename
+#ifndef NANO_TINY
+ , struct stat *die_stat
+#endif
+ )
+{
+ char *retval;
+ bool failed = TRUE;
+
+ /* If we're using restricted mode, don't write any emergency backup
+ * files, since that would allow reading from or writing to files
+ * not specified on the command line. */
+ if (ISSET(RESTRICTED))
+ return;
+
+ /* If we can't save, we have really bad problems, but we might as
+ * well try. */
+ if (*die_filename == '\0')
+ die_filename = "nano";
+
+ retval = get_next_filename(die_filename, ".save");
+ if (retval[0] != '\0')
+ failed = !write_file(retval, NULL, TRUE, OVERWRITE, TRUE);
+
+ if (!failed)
+ fprintf(stderr, _("\nBuffer written to %s\n"), retval);
+ else if (retval[0] != '\0')
+ fprintf(stderr, _("\nBuffer not written to %s: %s\n"), retval,
+ strerror(errno));
+ else
+ fprintf(stderr, _("\nBuffer not written: %s\n"),
+ _("Too many backup files?"));
+
+#ifndef NANO_TINY
+ /* Try and chmod/chown the save file to the values of the original file, but
+ dont worry if it fails because we're supposed to be bailing as fast
+ as possible. */
+ if (die_stat) {
+ int shush;
+ shush = chmod(retval, die_stat->st_mode);
+ shush = chown(retval, die_stat->st_uid, die_stat->st_gid);
+ }
+#endif
+
+ free(retval);
+}
+
+/* Initialize the three window portions nano uses. */
+void window_init(void)
+{
+ /* If the screen height is too small, get out. */
+ editwinrows = LINES - 5 + no_more_space() + no_help();
+ if (COLS < MIN_EDITOR_COLS || editwinrows < MIN_EDITOR_ROWS)
+ die(_("Window size is too small for nano...\n"));
+
+#ifndef DISABLE_WRAPJUSTIFY
+ /* Set up fill, based on the screen width. */
+ fill = wrap_at;
+ if (fill <= 0)
+ fill += COLS;
+ if (fill < 0)
+ fill = 0;
+#endif
+
+ if (topwin != NULL)
+ delwin(topwin);
+ if (edit != NULL)
+ delwin(edit);
+ if (bottomwin != NULL)
+ delwin(bottomwin);
+
+ /* Set up the windows. */
+ topwin = newwin(2 - no_more_space(), COLS, 0, 0);
+ edit = newwin(editwinrows, COLS, 2 - no_more_space(), 0);
+ bottomwin = newwin(3 - no_help(), COLS, editwinrows + (2 -
+ no_more_space()), 0);
+
+ /* Turn the keypad on for the windows, if necessary. */
+ if (!ISSET(REBIND_KEYPAD)) {
+ keypad(topwin, TRUE);
+ keypad(edit, TRUE);
+ keypad(bottomwin, TRUE);
+ }
+}
+
+#ifndef DISABLE_MOUSE
+/* Disable mouse support. */
+void disable_mouse_support(void)
+{
+ mousemask(0, NULL);
+ mouseinterval(oldinterval);
+}
+
+/* Enable mouse support. */
+void enable_mouse_support(void)
+{
+ mousemask(ALL_MOUSE_EVENTS, NULL);
+ oldinterval = mouseinterval(50);
+}
+
+/* Initialize mouse support. Enable it if the USE_MOUSE flag is set,
+ * and disable it otherwise. */
+void mouse_init(void)
+{
+ if (ISSET(USE_MOUSE))
+ enable_mouse_support();
+ else
+ disable_mouse_support();
+}
+#endif /* !DISABLE_MOUSE */
+
+#ifdef HAVE_GETOPT_LONG
+#define print_opt(shortflag, longflag, desc) print_opt_full(shortflag, longflag, desc)
+#else
+#define print_opt(shortflag, longflag, desc) print_opt_full(shortflag, desc)
+#endif
+
+/* Print one usage string to the screen. This cuts down on duplicate
+ * strings to translate, and leaves out the parts that shouldn't be
+ * translatable (i.e. the flag names). */
+void print_opt_full(const char *shortflag
+#ifdef HAVE_GETOPT_LONG
+ , const char *longflag
+#endif
+ , const char *desc)
+{
+ printf(" %s\t", shortflag);
+ if (strlenpt(shortflag) < 8)
+ printf("\t");
+
+#ifdef HAVE_GETOPT_LONG
+ printf("%s\t", longflag);
+ if (strlenpt(longflag) < 8)
+ printf("\t\t");
+ else if (strlenpt(longflag) < 16)
+ printf("\t");
+#endif
+
+ if (desc != NULL)
+ printf("%s", _(desc));
+ printf("\n");
+}
+
+/* Explain how to properly use nano and its command line options. */
+void usage(void)
+{
+ printf(_("Usage: nano [OPTIONS] [[+LINE,COLUMN] FILE]...\n\n"));
+ printf(
+#ifdef HAVE_GETOPT_LONG
+ _("Option\t\tGNU long option\t\tMeaning\n")
+#else
+ _("Option\t\tMeaning\n")
+#endif
+ );
+ print_opt("-h, -?", "--help", N_("Show this message"));
+ print_opt(_("+LINE,COLUMN"), "",
+ N_("Start at line LINE, column COLUMN"));
+#ifndef NANO_TINY
+ print_opt("-A", "--smarthome", N_("Enable smart home key"));
+ print_opt("-B", "--backup", N_("Save backups of existing files"));
+ print_opt(_("-C <dir>"), _("--backupdir=<dir>"),
+ N_("Directory for saving unique backup files"));
+#endif
+ print_opt("-D", "--boldtext",
+ N_("Use bold instead of reverse video text"));
+#ifndef NANO_TINY
+ print_opt("-E", "--tabstospaces",
+ N_("Convert typed tabs to spaces"));
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ print_opt("-F", "--multibuffer", N_("Enable multiple file buffers"));
+#endif
+#ifdef ENABLE_NANORC
+#ifndef NANO_TINY
+ print_opt("-H", "--historylog",
+ N_("Log & read search/replace string history"));
+#endif
+ print_opt("-I", "--ignorercfiles",
+ N_("Don't look at nanorc files"));
+#endif
+ print_opt("-K", "--rebindkeypad",
+ N_("Fix numeric keypad key confusion problem"));
+ print_opt("-L", "--nonewlines",
+ N_("Don't add newlines to the ends of files"));
+#ifndef NANO_TINY
+ print_opt("-N", "--noconvert",
+ N_("Don't convert files from DOS/Mac format"));
+#endif
+ print_opt("-O", "--morespace", N_("Use one more line for editing"));
+#ifndef NANO_TINY
+ print_opt("-P", "--poslog",
+ N_("Log & read location of cursor position"));
+#endif
+#ifndef DISABLE_JUSTIFY
+ print_opt(_("-Q <str>"), _("--quotestr=<str>"),
+ N_("Quoting string"));
+#endif
+ print_opt("-R", "--restricted", N_("Restricted mode"));
+#ifndef NANO_TINY
+ print_opt("-S", "--smooth",
+ N_("Scroll by line instead of half-screen"));
+#endif
+ print_opt(_("-T <#cols>"), _("--tabsize=<#cols>"),
+ N_("Set width of a tab to #cols columns"));
+#ifndef NANO_TINY
+ print_opt("-U", "--quickblank", N_("Do quick statusbar blanking"));
+#endif
+ print_opt("-V", "--version",
+ N_("Print version information and exit"));
+#ifndef NANO_TINY
+ print_opt("-W", "--wordbounds",
+ N_("Detect word boundaries more accurately"));
+#endif
+#ifdef ENABLE_COLOR
+ print_opt(_("-Y <str>"), _("--syntax=<str>"),
+ N_("Syntax definition to use for coloring"));
+#endif
+ print_opt("-c", "--const", N_("Constantly show cursor position"));
+ print_opt("-d", "--rebinddelete",
+ N_("Fix Backspace/Delete confusion problem"));
+#ifndef NANO_TINY
+ print_opt("-i", "--autoindent",
+ N_("Automatically indent new lines"));
+ print_opt("-k", "--cut", N_("Cut from cursor to end of line"));
+#endif
+ print_opt("-l", "--nofollow",
+ N_("Don't follow symbolic links, overwrite"));
+#ifndef DISABLE_MOUSE
+ print_opt("-m", "--mouse", N_("Enable the use of the mouse"));
+#endif
+#ifndef DISABLE_OPERATINGDIR
+ print_opt(_("-o <dir>"), _("--operatingdir=<dir>"),
+ N_("Set operating directory"));
+#endif
+ print_opt("-p", "--preserve",
+ N_("Preserve XON (^Q) and XOFF (^S) keys"));
+ print_opt("-q", "--quiet",
+ N_("Silently ignore startup issues like rc file errors"));
+#ifndef DISABLE_WRAPJUSTIFY
+ print_opt(_("-r <#cols>"), _("--fill=<#cols>"),
+ N_("Set wrapping point at column #cols"));
+#endif
+#ifndef DISABLE_SPELLER
+ print_opt(_("-s <prog>"), _("--speller=<prog>"),
+ N_("Enable alternate speller"));
+#endif
+ print_opt("-t", "--tempfile",
+ N_("Auto save on exit, don't prompt"));
+#ifndef NANO_TINY
+ print_opt("-u", "--undo", N_("Allow generic undo [EXPERIMENTAL]"));
+#endif
+
+ print_opt("-v", "--view", N_("View mode (read-only)"));
+#ifndef DISABLE_WRAPPING
+ print_opt("-w", "--nowrap", N_("Don't wrap long lines"));
+#endif
+ print_opt("-x", "--nohelp", N_("Don't show the two help lines"));
+ print_opt("-z", "--suspend", N_("Enable suspension"));
+ print_opt("-$", "--softwrap", N_("Enable soft line wrapping"));
+
+ /* This is a special case. */
+ print_opt("-a, -b, -e,", "", NULL);
+ print_opt("-f, -g, -j", "", N_("(ignored, for Pico compatibility)"));
+
+ exit(0);
+}
+
+/* Display the current version of nano, the date and time it was
+ * compiled, contact information for it, and the configuration options
+ * it was compiled with. */
+void version(void)
+{
+ printf(_(" GNU nano version %s (compiled %s, %s)\n"), VERSION,
+ __TIME__, __DATE__);
+ printf(" (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,\n");
+ printf(" 2008, 2009 Free Software Foundation, Inc.\n");
+ printf(
+ _(" Email: nano@nano-editor.org Web: http://www.nano-editor.org/"));
+ printf(_("\n Compiled options:"));
+
+#ifdef DISABLE_BROWSER
+ printf(" --disable-browser");
+#endif
+#ifdef DISABLE_HELP
+ printf(" --disable-help");
+#endif
+#ifdef DISABLE_JUSTIFY
+ printf(" --disable-justify");
+#endif
+#ifdef DISABLE_MOUSE
+ printf(" --disable-mouse");
+#endif
+#ifndef ENABLE_NLS
+ printf(" --disable-nls");
+#endif
+#ifdef DISABLE_OPERATINGDIR
+ printf(" --disable-operatingdir");
+#endif
+#ifdef DISABLE_SPELLER
+ printf(" --disable-speller");
+#endif
+#ifdef DISABLE_TABCOMP
+ printf(" --disable-tabcomp");
+#endif
+#ifdef DISABLE_WRAPPING
+ printf(" --disable-wrapping");
+#endif
+#ifdef DISABLE_ROOTWRAPPING
+ printf(" --disable-wrapping-as-root");
+#endif
+#ifdef ENABLE_COLOR
+ printf(" --enable-color");
+#endif
+#ifdef DEBUG
+ printf(" --enable-debug");
+#endif
+#ifdef NANO_EXTRA
+ printf(" --enable-extra");
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ printf(" --enable-multibuffer");
+#endif
+#ifdef ENABLE_NANORC
+ printf(" --enable-nanorc");
+#endif
+#ifdef NANO_TINY
+ printf(" --enable-tiny");
+#endif
+#ifdef ENABLE_UTF8
+ printf(" --enable-utf8");
+#endif
+#ifdef USE_SLANG
+ printf(" --with-slang");
+#endif
+ printf("\n");
+}
+
+/* Return 1 if the MORE_SPACE flag is set, and 0 otherwise. This is
+ * used to calculate the relative screen position while taking this flag
+ * into account, since it adds one line to the edit window. */
+int no_more_space(void)
+{
+ return ISSET(MORE_SPACE) ? 1 : 0;
+}
+
+/* Return 2 if the NO_HELP flag is set, and 0 otherwise. This is used
+ * to calculate the relative screen position while taking this flag into
+ * account, since it removes two lines from the edit window. */
+int no_help(void)
+{
+ return ISSET(NO_HELP) ? 2 : 0;
+}
+
+/* Indicate a disabled function on the statusbar. */
+void nano_disabled_msg(void)
+{
+ statusbar(_("Sorry, support for this function has been disabled"));
+}
+
+/* If the current file buffer has been modified, and the TEMP_FILE flag
+ * isn't set, ask whether or not to save the file buffer. If the
+ * TEMP_FILE flag is set, save it unconditionally. Then, if more than
+ * one file buffer is open, close the current file buffer and switch to
+ * the next one. If only one file buffer is open, exit from nano. */
+void do_exit(void)
+{
+ int i;
+
+ /* If the file hasn't been modified, pretend the user chose not to
+ * save. */
+ if (!openfile->modified)
+ i = 0;
+ /* If the TEMP_FILE flag is set, pretend the user chose to save. */
+ else if (ISSET(TEMP_FILE))
+ i = 1;
+ /* Otherwise, ask the user whether or not to save. */
+ else
+ i = do_yesno_prompt(FALSE,
+ _("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));
+
+#ifdef DEBUG
+ dump_filestruct(openfile->fileage);
+#endif
+
+ /* If the user chose not to save, or if the user chose to save and
+ * the save succeeded, we're ready to exit. */
+ if (i == 0 || (i == 1 && do_writeout(TRUE))) {
+#ifdef ENABLE_MULTIBUFFER
+ /* Exit only if there are no more open file buffers. */
+ if (!close_buffer())
+#endif
+ finish();
+ /* If the user canceled, we go on. */
+ } else if (i != 1)
+ statusbar(_("Cancelled"));
+
+ shortcut_init(FALSE);
+ display_main_list();
+}
+
+/* Another placeholder for function mapping */
+void do_cancel(void)
+{
+ ;
+}
+
+static struct sigaction pager_oldaction, pager_newaction; /* Original and temporary handlers for SIGINT. */
+static bool pager_sig_failed = FALSE; /* Did sigaction() fail without changing the signal handlers? */
+static bool pager_input_aborted = FALSE; /* Did someone invoke the pager and abort it via ^C? */
+
+
+/* Things which need to be run regardless of whether
+ we finished the stdin pipe correctly or not */
+void finish_stdin_pager(void)
+{
+ FILE *f;
+ int ttystdin;
+
+ /* Read whatever we did get from stdin */
+ f = fopen("/dev/stdin", "rb");
+ if (f == NULL)
+ nperror("fopen");
+
+ read_file(f, 0, "stdin", TRUE, FALSE);
+ ttystdin = open("/dev/tty", O_RDONLY);
+ if (!ttystdin)
+ die(_("Couldn't reopen stdin from keyboard, sorry\n"));
+
+ dup2(ttystdin,0);
+ close(ttystdin);
+ if (!pager_input_aborted)
+ tcgetattr(0, &oldterm);
+ if (!pager_sig_failed && sigaction(SIGINT, &pager_oldaction, NULL) == -1)
+ nperror("sigaction");
+ terminal_init();
+ doupdate();
+}
+
+
+/* Cancel reading from stdin like a pager */
+RETSIGTYPE cancel_stdin_pager(int signal)
+{
+ /* Currently do nothing, just handle the intr silently */
+ pager_input_aborted = TRUE;
+}
+
+/* Let nano read stdin for the first file at least */
+void stdin_pager(void)
+{
+ endwin();
+ if (!pager_input_aborted)
+ tcsetattr(0, TCSANOW, &oldterm);
+ fprintf(stderr, _("Reading from stdin, ^C to abort\n"));
+
+ /* Set things up so that Ctrl-C will cancel the new process. */
+ /* Enable interpretation of the special control keys so that we get
+ * SIGINT when Ctrl-C is pressed. */
+#ifndef NANO_TINY
+ enable_signals();
+#endif
+
+ if (sigaction(SIGINT, NULL, &pager_newaction) == -1) {
+ pager_sig_failed = TRUE;
+ nperror("sigaction");
+ } else {
+ pager_newaction.sa_handler = cancel_stdin_pager;
+ if (sigaction(SIGINT, &pager_newaction, &pager_oldaction) == -1) {
+ pager_sig_failed = TRUE;
+ nperror("sigaction");
+ }
+ }
+
+ open_buffer("", FALSE);
+ finish_stdin_pager();
+}
+
+
+
+/* Initialize the signal handlers. */
+void signal_init(void)
+{
+ /* Trap SIGINT and SIGQUIT because we want them to do useful
+ * things. */
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGQUIT, &act, NULL);
+
+ /* Trap SIGHUP and SIGTERM because we want to write the file out. */
+ act.sa_handler = handle_hupterm;
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+
+#ifndef NANO_TINY
+ /* Trap SIGWINCH because we want to handle window resizes. */
+ act.sa_handler = handle_sigwinch;
+ sigaction(SIGWINCH, &act, NULL);
+ allow_pending_sigwinch(FALSE);
+#endif
+
+ /* Trap normal suspend (^Z) so we can handle it ourselves. */
+ if (!ISSET(SUSPEND)) {
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGTSTP, &act, NULL);
+ } else {
+ /* Block all other signals in the suspend and continue handlers.
+ * If we don't do this, other stuff interrupts them! */
+ sigfillset(&act.sa_mask);
+
+ act.sa_handler = do_suspend;
+ sigaction(SIGTSTP, &act, NULL);
+
+ act.sa_handler = do_continue;
+ sigaction(SIGCONT, &act, NULL);
+ }
+}
+
+/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
+RETSIGTYPE handle_hupterm(int signal)
+{
+ die(_("Received SIGHUP or SIGTERM\n"));
+}
+
+/* Handler for SIGTSTP (suspend). */
+RETSIGTYPE do_suspend(int signal)
+{
+
+ if (ISSET(RESTRICTED)) {
+ nano_disabled_msg();
+ return;
+ }
+
+#ifndef DISABLE_MOUSE
+ /* Turn mouse support off. */
+ disable_mouse_support();
+#endif
+
+ /* Move the cursor to the last line of the screen. */
+ move(LINES - 1, 0);
+ endwin();
+
+ /* Display our helpful message. */
+ printf(_("Use \"fg\" to return to nano.\n"));
+ fflush(stdout);
+
+ /* Restore the old terminal settings. */
+ tcsetattr(0, TCSANOW, &oldterm);
+
+ /* Trap SIGHUP and SIGTERM so we can properly deal with them while
+ * suspended. */
+ act.sa_handler = handle_hupterm;
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+
+ /* Do what mutt does: send ourselves a SIGSTOP. */
+ kill(0, SIGSTOP);
+}
+
+/* the subnfunc version */
+void do_suspend_void(void)
+{
+ if (ISSET(SUSPEND))
+ do_suspend(0);
+}
+
+/* Handler for SIGCONT (continue after suspend). */
+RETSIGTYPE do_continue(int signal)
+{
+#ifndef DISABLE_MOUSE
+ /* Turn mouse support back on if it was on before. */
+ if (ISSET(USE_MOUSE))
+ enable_mouse_support();
+#endif
+
+#ifndef NANO_TINY
+ /* Perhaps the user resized the window while we slept. Handle it,
+ * and restore the terminal to its previous state and update the
+ * screen in the process. */
+ handle_sigwinch(0);
+#else
+ /* Restore the terminal to its previous state. */
+ terminal_init();
+
+ /* Turn the cursor back on for sure. */
+ curs_set(1);
+
+ /* Redraw the contents of the windows that need it. */
+ blank_statusbar();
+ wnoutrefresh(bottomwin);
+ total_refresh();
+#endif
+}
+
+#ifndef NANO_TINY
+/* Handler for SIGWINCH (window size change). */
+RETSIGTYPE handle_sigwinch(int signal)
+{
+ const char *tty = ttyname(0);
+ int fd, result = 0;
+ struct winsize win;
+
+ if (tty == NULL)
+ return;
+ fd = open(tty, O_RDWR);
+ if (fd == -1)
+ return;
+ result = ioctl(fd, TIOCGWINSZ, &win);
+ close(fd);
+ if (result == -1)
+ return;
+
+ /* We could check whether the COLS or LINES changed, and return
+ * otherwise. However, COLS and LINES are curses global variables,
+ * and in some cases curses has already updated them. But not in
+ * all cases. Argh. */
+#ifdef REDEFINIG_MACROS_OK
+ COLS = win.ws_col;
+ LINES = win.ws_row;
+#endif
+
+ /* If we've partitioned the filestruct, unpartition it now. */
+ if (filepart != NULL)
+ unpartition_filestruct(&filepart);
+
+#ifdef USE_SLANG
+ /* Slang curses emulation brain damage, part 1: If we just do what
+ * curses does here, it'll only work properly if the resize made the
+ * window smaller. Do what mutt does: Leave and immediately reenter
+ * Slang screen management mode. */
+ SLsmg_reset_smg();
+ SLsmg_init_smg();
+#else
+ /* Do the equivalent of what Minimum Profit does: Leave and
+ * immediately reenter curses mode. */
+ endwin();
+ doupdate();
+#endif
+
+ /* Restore the terminal to its previous state. */
+ terminal_init();
+
+ /* Turn the cursor back on for sure. */
+ curs_set(1);
+
+ /* Do the equivalent of what both mutt and Minimum Profit do:
+ * Reinitialize all the windows based on the new screen
+ * dimensions. */
+ window_init();
+
+ /* Redraw the contents of the windows that need it. */
+ blank_statusbar();
+ wnoutrefresh(bottomwin);
+ currmenu = MMAIN;
+ total_refresh();
+
+ /* Jump back to either main() or the unjustify routine in
+ * do_justify(). */
+ siglongjmp(jump_buf, 1);
+}
+
+/* If allow is TRUE, block any SIGWINCH signals that we get, so that we
+ * can deal with them later. If allow is FALSE, unblock any SIGWINCH
+ * signals that we have, so that we can deal with them now. */
+void allow_pending_sigwinch(bool allow)
+{
+ sigset_t winch;
+ sigemptyset(&winch);
+ sigaddset(&winch, SIGWINCH);
+ sigprocmask(allow ? SIG_UNBLOCK : SIG_BLOCK, &winch, NULL);
+}
+#endif /* !NANO_TINY */
+
+#ifndef NANO_TINY
+/* Handle the global toggle specified in which. */
+void do_toggle(int flag)
+{
+ bool enabled;
+ char *desc;
+
+ TOGGLE(flag);
+
+ switch (flag) {
+#ifndef DISABLE_MOUSE
+ case USE_MOUSE:
+ mouse_init();
+ break;
+#endif
+ case MORE_SPACE:
+ case NO_HELP:
+ window_init();
+ total_refresh();
+ break;
+ case SUSPEND:
+ signal_init();
+ break;
+#ifdef ENABLE_NANORC
+ case WHITESPACE_DISPLAY:
+ titlebar(NULL);
+ edit_refresh();
+ break;
+#endif
+#ifdef ENABLE_COLOR
+ case NO_COLOR_SYNTAX:
+ edit_refresh();
+ break;
+#endif
+ case SOFTWRAP:
+ total_refresh();
+ break;
+ }
+
+ enabled = ISSET(flag);
+
+ if (flag == NO_HELP
+#ifndef DISABLE_WRAPPING
+ || flag == NO_WRAP
+#endif
+#ifdef ENABLE_COLOR
+ || flag == NO_COLOR_SYNTAX
+#endif
+ )
+ enabled = !enabled;
+
+ desc = _(flagtostr(flag));
+ statusbar("%s %s", desc, enabled ? _("enabled") :
+ _("disabled"));
+}
+
+/* Bleh */
+void do_toggle_void(void)
+{
+;
+}
+#endif /* !NANO_TINY */
+
+/* Disable extended input and output processing in our terminal
+ * settings. */
+void disable_extended_io(void)
+{
+ struct termios term;
+
+ tcgetattr(0, &term);
+ term.c_lflag &= ~IEXTEN;
+ term.c_oflag &= ~OPOST;
+ tcsetattr(0, TCSANOW, &term);
+}
+
+/* Disable interpretation of the special control keys in our terminal
+ * settings. */
+void disable_signals(void)
+{
+ struct termios term;
+
+ tcgetattr(0, &term);
+ term.c_lflag &= ~ISIG;
+ tcsetattr(0, TCSANOW, &term);
+}
+
+#ifndef NANO_TINY
+/* Enable interpretation of the special control keys in our terminal
+ * settings. */
+void enable_signals(void)
+{
+ struct termios term;
+
+ tcgetattr(0, &term);
+ term.c_lflag |= ISIG;
+ tcsetattr(0, TCSANOW, &term);
+}
+#endif
+
+/* Disable interpretation of the flow control characters in our terminal
+ * settings. */
+void disable_flow_control(void)
+{
+ struct termios term;
+
+ tcgetattr(0, &term);
+ term.c_iflag &= ~IXON;
+ tcsetattr(0, TCSANOW, &term);
+}
+
+/* Enable interpretation of the flow control characters in our terminal
+ * settings. */
+void enable_flow_control(void)
+{
+ struct termios term;
+
+ tcgetattr(0, &term);
+ term.c_iflag |= IXON;
+ tcsetattr(0, TCSANOW, &term);
+}
+
+/* Set up the terminal state. Put the terminal in raw mode (read one
+ * character at a time, disable the special control keys, and disable
+ * the flow control characters), disable translation of carriage return
+ * (^M) into newline (^J) so that we can tell the difference between the
+ * Enter key and Ctrl-J, and disable echoing of characters as they're
+ * typed. Finally, disable extended input and output processing, and,
+ * if we're not in preserve mode, reenable interpretation of the flow
+ * control characters. */
+void terminal_init(void)
+{
+#ifdef USE_SLANG
+ /* Slang curses emulation brain damage, part 2: Slang doesn't
+ * implement raw(), nonl(), or noecho() properly, so there's no way
+ * to properly reinitialize the terminal using them. We have to
+ * disable the special control keys and interpretation of the flow
+ * control characters using termios, save the terminal state after
+ * the first call, and restore it on subsequent calls. */
+ static struct termios newterm;
+ static bool newterm_set = FALSE;
+
+ if (!newterm_set) {
+#endif
+
+ raw();
+ nonl();
+ noecho();
+ disable_extended_io();
+ if (ISSET(PRESERVE))
+ enable_flow_control();
+
+ disable_signals();
+#ifdef USE_SLANG
+ if (!ISSET(PRESERVE))
+ disable_flow_control();
+
+ tcgetattr(0, &newterm);
+ newterm_set = TRUE;
+ } else
+ tcsetattr(0, TCSANOW, &newterm);
+#endif
+}
+
+/* Read in a character, interpret it as a shortcut or toggle if
+ * necessary, and return it. Set meta_key to TRUE if the character is a
+ * meta sequence, set func_key to TRUE if the character is a function
+ * key, set s_or_t to TRUE if the character is a shortcut or toggle
+ * key, set ran_func to TRUE if we ran a function associated with a
+ * shortcut key, and set finished to TRUE if we're done after running
+ * or trying to run a function associated with a shortcut key. If
+ * allow_funcs is FALSE, don't actually run any functions associated
+ * with shortcut keys. */
+int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
+ *ran_func, bool *finished, bool allow_funcs)
+{
+ int input;
+ /* The character we read in. */
+ static int *kbinput = NULL;
+ /* The input buffer. */
+ static size_t kbinput_len = 0;
+ /* The length of the input buffer. */
+ bool cut_copy = FALSE;
+ /* Are we cutting or copying text? */
+ const sc *s;
+ bool have_shortcut;
+
+ *s_or_t = FALSE;
+ *ran_func = FALSE;
+ *finished = FALSE;
+
+ /* Read in a character. */
+ input = get_kbinput(edit, meta_key, func_key);
+
+#ifndef DISABLE_MOUSE
+ if (allow_funcs) {
+ /* If we got a mouse click and it was on a shortcut, read in the
+ * shortcut character. */
+ if (*func_key && input == KEY_MOUSE) {
+ if (do_mouse() == 1)
+ input = get_kbinput(edit, meta_key, func_key);
+ else {
+ *meta_key = FALSE;
+ *func_key = FALSE;
+ input = ERR;
+ }
+ }
+ }
+#endif
+
+ /* Check for a shortcut in the main list. */
+ s = get_shortcut(MMAIN, &input, meta_key, func_key);
+
+ /* If we got a shortcut from the main list, or a "universal"
+ * edit window shortcut, set have_shortcut to TRUE. */
+ have_shortcut = (s != NULL);
+
+ /* If we got a non-high-bit control key, a meta key sequence, or a
+ * function key, and it's not a shortcut or toggle, throw it out. */
+ if (!have_shortcut) {
+ if (is_ascii_cntrl_char(input) || *meta_key || *func_key) {
+ statusbar(_("Unknown Command"));
+ beep();
+ *meta_key = FALSE;
+ *func_key = FALSE;
+ input = ERR;
+ }
+ }
+
+ if (allow_funcs) {
+ /* If we got a character, and it isn't a shortcut or toggle,
+ * it's a normal text character. Display the warning if we're
+ * in view mode, or add the character to the input buffer if
+ * we're not. */
+ if (input != ERR && !have_shortcut) {
+ if (ISSET(VIEW_MODE))
+ print_view_warning();
+ else {
+ kbinput_len++;
+ kbinput = (int *)nrealloc(kbinput, kbinput_len *
+ sizeof(int));
+ kbinput[kbinput_len - 1] = input;
+
+ }
+ }
+
+ /* If we got a shortcut or toggle, or if there aren't any other
+ * characters waiting after the one we read in, we need to
+ * output all the characters in the input buffer if it isn't
+ * empty. Note that it should be empty if we're in view
+ * mode. */
+ if (have_shortcut || get_key_buffer_len() == 0) {
+#ifndef DISABLE_WRAPPING
+ /* If we got a shortcut or toggle, and it's not the shortcut
+ * for verbatim input, turn off prepending of wrapped
+ * text. */
+ if (have_shortcut && (!have_shortcut || s == NULL || s->scfunc !=
+ do_verbatim_input))
+ wrap_reset();
+#endif
+
+ if (kbinput != NULL) {
+ /* Display all the characters in the input buffer at
+ * once, filtering out control characters. */
+ char *output = charalloc(kbinput_len + 1);
+ size_t i;
+
+ for (i = 0; i < kbinput_len; i++)
+ output[i] = (char)kbinput[i];
+ output[i] = '\0';
+
+ do_output(output, kbinput_len, FALSE);
+
+ free(output);
+
+ /* Empty the input buffer. */
+ kbinput_len = 0;
+ free(kbinput);
+ kbinput = NULL;
+ }
+ }
+
+ if (have_shortcut) {
+ switch (input) {
+ /* Handle the normal edit window shortcuts, setting
+ * ran_func to TRUE if we try to run their associated
+ * functions and setting finished to TRUE to indicate
+ * that we're done after running or trying to run their
+ * associated functions. */
+ default:
+ /* If the function associated with this shortcut is
+ * cutting or copying text, indicate this. */
+ if (s->scfunc == do_cut_text_void
+#ifndef NANO_TINY
+ || s->scfunc == do_copy_text || s->scfunc ==
+ do_cut_till_end
+#endif
+ )
+ cut_copy = TRUE;
+
+ if (s->scfunc != 0) {
+ const subnfunc *f = sctofunc((sc *) s);
+ *ran_func = TRUE;
+ if (ISSET(VIEW_MODE) && f && !f->viewok)
+ print_view_warning();
+ else {
+#ifndef NANO_TINY
+ if (s->scfunc == do_toggle_void)
+ do_toggle(s->toggle);
+ else {
+#else
+ {
+#endif
+ s->scfunc();
+#ifdef ENABLE_COLOR
+ if (f && !f->viewok && openfile->syntax != NULL
+ && openfile->syntax->nmultis > 0) {
+ reset_multis(openfile->current, FALSE);
+ }
+#endif
+ if (edit_refresh_needed) {
+#ifdef DEBUG
+ fprintf(stderr, "running edit_refresh() as edit_refresh_needed is true\n");
+#endif
+ edit_refresh();
+ edit_refresh_needed = FALSE;
+ }
+
+ }
+ }
+ }
+ *finished = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* If we aren't cutting or copying text, blow away the text in the
+ * cutbuffer. */
+ if (!cut_copy)
+ cutbuffer_reset();
+
+ return input;
+}
+
+void xon_complaint(void)
+{
+ statusbar(_("XON ignored, mumble mumble"));
+}
+
+void xoff_complaint(void)
+{
+ statusbar(_("XOFF ignored, mumble mumble"));
+}
+
+
+#ifndef DISABLE_MOUSE
+/* Handle a mouse click on the edit window or the shortcut list. */
+int do_mouse(void)
+{
+ int mouse_x, mouse_y;
+ int retval = get_mouseinput(&mouse_x, &mouse_y, TRUE);
+
+ /* We can click on the edit window to move the cursor. */
+ if (retval == 0 && wmouse_trafo(edit, &mouse_y, &mouse_x, FALSE)) {
+ bool sameline;
+ /* Did they click on the line with the cursor? If they
+ * clicked on the cursor, we set the mark. */
+ filestruct *current_save = openfile->current;
+ size_t current_x_save = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+
+ sameline = (mouse_y == openfile->current_y);
+
+#ifdef DEBUG
+ fprintf(stderr, "mouse_y = %d, current_y = %d\n", mouse_y, openfile->current_y);
+#endif
+
+ if (ISSET(SOFTWRAP)) {
+ int i = 0;
+ for (openfile->current = openfile->edittop;
+ openfile->current->next && i < mouse_y;
+ openfile->current = openfile->current->next, i++) {
+ openfile->current_y = i;
+ i += strlenpt(openfile->current->data) / COLS;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "do_mouse(): moving to current_y = %d, i %d\n", openfile->current_y, i);
+ fprintf(stderr, " openfile->current->data = \"%s\"\n", openfile->current->data);
+#endif
+
+ if (i > mouse_y) {
+ openfile->current = openfile->current->prev;
+ openfile->current_x = actual_x(openfile->current->data, mouse_x + (mouse_y - openfile->current_y) * COLS);
+#ifdef DEBUG
+ fprintf(stderr, "do_mouse(): i > mouse_y, mouse_x = %d, current_x to = %d\n", mouse_x, openfile->current_x);
+#endif
+ } else {
+ openfile->current_x = actual_x(openfile->current->data, mouse_x);
+#ifdef DEBUG
+ fprintf(stderr, "do_mouse(): i <= mouse_y, mouse_x = %d, setting current_x to = %d\n", mouse_x, openfile->current_x);
+#endif
+ }
+
+ openfile->placewewant = xplustabs();
+
+ } else {
+ /* Move to where the click occurred. */
+ for (; openfile->current_y < mouse_y && openfile->current !=
+ openfile->filebot; openfile->current_y++)
+ openfile->current = openfile->current->next;
+ for (; openfile->current_y > mouse_y && openfile->current !=
+ openfile->fileage; openfile->current_y--)
+ openfile->current = openfile->current->prev;
+
+ openfile->current_x = actual_x(openfile->current->data,
+ get_page_start(xplustabs()) + mouse_x);
+
+ openfile->placewewant = xplustabs();
+ }
+
+#ifndef NANO_TINY
+ /* Clicking where the cursor is toggles the mark, as does
+ * clicking beyond the line length with the cursor at the end of
+ * the line. */
+ if (sameline && openfile->current_x == current_x_save)
+ do_mark();
+#endif
+
+ edit_redraw(current_save, pww_save);
+ }
+
+ return retval;
+}
+#endif /* !DISABLE_MOUSE */
+
+#ifdef ENABLE_COLOR
+void alloc_multidata_if_needed(filestruct *fileptr)
+{
+ if (!fileptr->multidata)
+ fileptr->multidata = (short *) nmalloc(openfile->syntax->nmultis * sizeof(short));
+}
+
+/* Precalculate the multi-line start and end regex info so we can speed up
+ rendering (with any hope at all...) */
+void precalc_multicolorinfo(void)
+{
+#ifdef DEBUG
+ fprintf(stderr, "entering precalc_multicolorinfo()\n");
+#endif
+ if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
+ const colortype *tmpcolor = openfile->colorstrings;
+ regmatch_t startmatch, endmatch;
+ filestruct *fileptr, *endptr;
+ time_t last_check = time(NULL), cur_check = 0;
+
+ /* Let us get keypresses to see if the user is trying to
+ start editing. We may want to throw up a statusbar
+ message before starting this later if it takes
+ too long to do this routine. For now silently
+ abort if they hit a key */
+ nodelay(edit, FALSE);
+
+ for (; tmpcolor != NULL; tmpcolor = tmpcolor->next) {
+
+ /* If it's not a multi-line regex, amscray */
+ if (tmpcolor->end == NULL)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "working on color id %d\n", tmpcolor->id);
+#endif
+
+
+ for (fileptr = openfile->fileage; fileptr != NULL; fileptr = fileptr->next) {
+ int startx = 0;
+ int nostart = 0;
+
+
+#ifdef DEBUG
+ fprintf(stderr, "working on lineno %lu\n", (unsigned long) fileptr->lineno);
+#endif
+
+ alloc_multidata_if_needed(fileptr);
+
+ if ((cur_check = time(NULL)) - last_check > 1) {
+ last_check = cur_check;
+ if (wgetch(edit) != ERR)
+ goto precalc_cleanup;
+ }
+
+ while ((nostart = regexec(tmpcolor->start, &fileptr->data[startx], 1, &startmatch, 0)) == 0) {
+ /* Look for end and start marking how many lines are encompassed
+ whcih should speed up rendering later */
+ startx += startmatch.rm_eo;
+#ifdef DEBUG
+ fprintf(stderr, "match found at pos %d...", startx);
+#endif
+
+ /* Look on this line first for end */
+ if (regexec(tmpcolor->end, &fileptr->data[startx], 1, &endmatch, 0) == 0) {
+ startx += endmatch.rm_eo;
+ fileptr->multidata[tmpcolor->id] |= CSTARTENDHERE;
+#ifdef DEBUG
+ fprintf(stderr, "end found on this line\n");
+#endif
+ continue;
+ }
+
+ /* Nice, we didn't find the end regex on this line. Let's start looking for it */
+ for (endptr = fileptr->next; endptr != NULL; endptr = endptr->next) {
+
+#ifdef DEBUG
+ fprintf(stderr, "advancing to line %lu to find end...\n", (unsigned long) endptr->lineno);
+#endif
+ /* Check for keyboard input again */
+ if ((cur_check = time(NULL)) - last_check > 1) {
+ last_check = cur_check;
+ if (wgetch(edit) != ERR)
+ goto precalc_cleanup;
+ }
+ if (regexec(tmpcolor->end, endptr->data, 1, &endmatch, 0) == 0)
+ break;
+ }
+
+ if (endptr == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "no end found, breaking out\n");
+#endif
+ break;
+ }
+
+
+#ifdef DEBUG
+ fprintf(stderr, "end found\n");
+#endif
+
+ /* We found it, we found it, la la la la la. Mark all the
+ lines in between and the ends properly */
+ fileptr->multidata[tmpcolor->id] |= CENDAFTER;
+#ifdef DEBUG
+ fprintf(stderr, "marking line %lu as CENDAFTER\n", (unsigned long) fileptr->lineno);
+#endif
+ for (fileptr = fileptr->next; fileptr != endptr; fileptr = fileptr->next) {
+ alloc_multidata_if_needed(fileptr);
+ fileptr->multidata[tmpcolor->id] = CWHOLELINE;
+#ifdef DEBUG
+ fprintf(stderr, "marking intermediary line %lu as CWHOLELINE\n", (unsigned long) fileptr->lineno);
+#endif
+ }
+ alloc_multidata_if_needed(endptr);
+#ifdef DEBUG
+ fprintf(stderr, "marking line %lu as BEGINBEFORE\n", (unsigned long) fileptr->lineno);
+#endif
+ endptr->multidata[tmpcolor->id] |= CBEGINBEFORE;
+ /* We should be able to skip all the way to the line of the match.
+ This may introduce more bugs but it's the Right Thing to do */
+ fileptr = endptr;
+ startx = endmatch.rm_eo;
+#ifdef DEBUG
+ fprintf(stderr, "jumping to line %lu pos %d to continue\n", (unsigned long) endptr->lineno, startx);
+#endif
+ }
+ if (nostart && startx == 0) {
+#ifdef DEBUG
+ fprintf(stderr, "no start found on line %lu, continuing\n", (unsigned long) fileptr->lineno);
+#endif
+ fileptr->multidata[tmpcolor->id] = CNONE;
+ continue;
+ }
+ }
+ }
+ }
+precalc_cleanup:
+ nodelay(edit, FALSE);
+}
+#endif /* ENABLE_COLOR */
+
+/* The user typed output_len multibyte characters. Add them to the edit
+ * buffer, filtering out all ASCII control characters if allow_cntrls is
+ * TRUE. */
+void do_output(char *output, size_t output_len, bool allow_cntrls)
+{
+ size_t current_len, orig_lenpt, i = 0;
+ char *char_buf = charalloc(mb_cur_max());
+ int char_buf_len;
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ current_len = strlen(openfile->current->data);
+ if (ISSET(SOFTWRAP))
+ orig_lenpt = strlenpt(openfile->current->data);
+
+ while (i < output_len) {
+ /* If allow_cntrls is TRUE, convert nulls and newlines
+ * properly. */
+ if (allow_cntrls) {
+ /* Null to newline, if needed. */
+ if (output[i] == '\0')
+ output[i] = '\n';
+ /* Newline to Enter, if needed. */
+ else if (output[i] == '\n') {
+ do_enter(FALSE);
+ i++;
+ continue;
+ }
+ }
+
+ /* Interpret the next multibyte character. */
+ char_buf_len = parse_mbchar(output + i, char_buf, NULL);
+
+ i += char_buf_len;
+
+ /* If allow_cntrls is FALSE, filter out an ASCII control
+ * character. */
+ if (!allow_cntrls && is_ascii_cntrl_char(*(output + i -
+ char_buf_len)))
+ continue;
+
+ /* If the NO_NEWLINES flag isn't set, when a character is
+ * added to the magicline, it means we need a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot ==
+ openfile->current)
+ new_magicline();
+
+ /* More dangerousness fun =) */
+ openfile->current->data = charealloc(openfile->current->data,
+ current_len + (char_buf_len * 2));
+
+ assert(openfile->current_x <= current_len);
+
+ charmove(openfile->current->data + openfile->current_x +
+ char_buf_len, openfile->current->data +
+ openfile->current_x, current_len - openfile->current_x +
+ char_buf_len);
+ strncpy(openfile->current->data + openfile->current_x, char_buf,
+ char_buf_len);
+ current_len += char_buf_len;
+ openfile->totsize++;
+ set_modified();
+
+#ifndef NANO_TINY
+ update_undo(ADD);
+
+ /* Note that current_x has not yet been incremented. */
+ if (openfile->mark_set && openfile->current ==
+ openfile->mark_begin && openfile->current_x <
+ openfile->mark_begin_x)
+ openfile->mark_begin_x += char_buf_len;
+#endif
+
+ openfile->current_x += char_buf_len;
+
+#ifndef DISABLE_WRAPPING
+ /* If we're wrapping text, we need to call edit_refresh(). */
+ if (!ISSET(NO_WRAP))
+ if (do_wrap(openfile->current, FALSE))
+ edit_refresh_needed = TRUE;
+#endif
+
+#ifdef ENABLE_COLOR
+ /* If color syntaxes are available and turned on, we need to
+ * call edit_refresh(). */
+ if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX))
+ edit_refresh_needed = TRUE;
+#endif
+ }
+
+ /* Well we might also need a full refresh if we've changed the
+ line length to be a new multiple of COLS */
+ if (ISSET(SOFTWRAP) && edit_refresh_needed == FALSE)
+ if (strlenpt(openfile->current->data) / COLS != orig_lenpt / COLS)
+ edit_refresh_needed = TRUE;
+
+ free(char_buf);
+
+ openfile->placewewant = xplustabs();
+
+
+#ifdef ENABLE_COLOR
+ reset_multis(openfile->current, FALSE);
+#endif
+ if (edit_refresh_needed == TRUE) {
+ edit_refresh();
+ edit_refresh_needed = FALSE;
+ } else
+ update_line(openfile->current, openfile->current_x);
+}
+
+int main(int argc, char **argv)
+{
+ int optchr;
+ ssize_t startline = 1;
+ /* Line to try and start at. */
+ ssize_t startcol = 1;
+ /* Column to try and start at. */
+#ifndef DISABLE_WRAPJUSTIFY
+ bool fill_used = FALSE;
+ /* Was the fill option used? */
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ bool old_multibuffer;
+ /* The old value of the multibuffer option, restored after we
+ * load all files on the command line. */
+#endif
+#ifdef HAVE_GETOPT_LONG
+ const struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+ {"boldtext", 0, NULL, 'D'},
+#ifdef ENABLE_MULTIBUFFER
+ {"multibuffer", 0, NULL, 'F'},
+#endif
+#ifdef ENABLE_NANORC
+ {"ignorercfiles", 0, NULL, 'I'},
+#endif
+ {"rebindkeypad", 0, NULL, 'K'},
+ {"nonewlines", 0, NULL, 'L'},
+ {"morespace", 0, NULL, 'O'},
+#ifndef DISABLE_JUSTIFY
+ {"quotestr", 1, NULL, 'Q'},
+#endif
+ {"restricted", 0, NULL, 'R'},
+ {"tabsize", 1, NULL, 'T'},
+ {"version", 0, NULL, 'V'},
+#ifdef ENABLE_COLOR
+ {"syntax", 1, NULL, 'Y'},
+#endif
+ {"const", 0, NULL, 'c'},
+ {"rebinddelete", 0, NULL, 'd'},
+ {"nofollow", 0, NULL, 'l'},
+#ifndef DISABLE_MOUSE
+ {"mouse", 0, NULL, 'm'},
+#endif
+#ifndef DISABLE_OPERATINGDIR
+ {"operatingdir", 1, NULL, 'o'},
+#endif
+ {"preserve", 0, NULL, 'p'},
+ {"quiet", 0, NULL, 'q'},
+#ifndef DISABLE_WRAPJUSTIFY
+ {"fill", 1, NULL, 'r'},
+#endif
+#ifndef DISABLE_SPELLER
+ {"speller", 1, NULL, 's'},
+#endif
+ {"tempfile", 0, NULL, 't'},
+ {"view", 0, NULL, 'v'},
+#ifndef DISABLE_WRAPPING
+ {"nowrap", 0, NULL, 'w'},
+#endif
+ {"nohelp", 0, NULL, 'x'},
+ {"suspend", 0, NULL, 'z'},
+#ifndef NANO_TINY
+ {"smarthome", 0, NULL, 'A'},
+ {"backup", 0, NULL, 'B'},
+ {"backupdir", 1, NULL, 'C'},
+ {"tabstospaces", 0, NULL, 'E'},
+ {"historylog", 0, NULL, 'H'},
+ {"noconvert", 0, NULL, 'N'},
+ {"poslog", 0, NULL, 'P'},
+ {"smooth", 0, NULL, 'S'},
+ {"quickblank", 0, NULL, 'U'},
+ {"undo", 0, NULL, 'u'},
+ {"wordbounds", 0, NULL, 'W'},
+ {"autoindent", 0, NULL, 'i'},
+ {"cut", 0, NULL, 'k'},
+ {"softwrap", 0, NULL, '$'},
+#endif
+ {NULL, 0, NULL, 0}
+ };
+#endif
+
+#ifdef ENABLE_UTF8
+ {
+ /* If the locale set exists and uses UTF-8, we should use
+ * UTF-8. */
+ char *locale = setlocale(LC_ALL, "");
+
+ if (locale != NULL && (strcmp(nl_langinfo(CODESET),
+ "UTF-8") == 0)) {
+#ifdef USE_SLANG
+ SLutf8_enable(1);
+#endif
+ utf8_init();
+ }
+ }
+#else
+ setlocale(LC_ALL, "");
+#endif
+
+#ifdef ENABLE_NLS
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+#endif
+
+#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAPPING)
+ /* If we don't have rcfile support, --disable-wrapping-as-root is
+ * used, and we're root, turn wrapping off. */
+ if (geteuid() == NANO_ROOT_UID)
+ SET(NO_WRAP);
+#endif
+
+ while ((optchr =
+#ifdef HAVE_GETOPT_LONG
+ getopt_long(argc, argv,
+ "h?ABC:DEFHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$",
+ long_options, NULL)
+#else
+ getopt(argc, argv,
+ "h?ABC:DEFHIKLNOPQ:RST:UVWY:abcdefgijklmo:pqr:s:tuvwxz$")
+#endif
+ ) != -1) {
+ switch (optchr) {
+ case 'a':
+ case 'b':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'j':
+ /* Pico compatibility flags. */
+ break;
+#ifndef NANO_TINY
+ case 'A':
+ SET(SMART_HOME);
+ break;
+ case 'B':
+ SET(BACKUP_FILE);
+ break;
+ case 'C':
+ backup_dir = mallocstrcpy(backup_dir, optarg);
+ break;
+#endif
+ case 'D':
+ SET(BOLD_TEXT);
+ break;
+#ifndef NANO_TINY
+ case 'E':
+ SET(TABS_TO_SPACES);
+ break;
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ case 'F':
+ SET(MULTIBUFFER);
+ break;
+#endif
+#ifdef ENABLE_NANORC
+#ifndef NANO_TINY
+ case 'H':
+ SET(HISTORYLOG);
+ break;
+#endif
+ case 'I':
+ no_rcfiles = TRUE;
+ break;
+#endif
+ case 'K':
+ SET(REBIND_KEYPAD);
+ break;
+ case 'L':
+ SET(NO_NEWLINES);
+ break;
+#ifndef NANO_TINY
+ case 'N':
+ SET(NO_CONVERT);
+ break;
+#endif
+ case 'O':
+ SET(MORE_SPACE);
+ break;
+#ifndef NANO_TINY
+ case 'P':
+ SET(POS_HISTORY);
+ break;
+#endif
+#ifndef DISABLE_JUSTIFY
+ case 'Q':
+ quotestr = mallocstrcpy(quotestr, optarg);
+ break;
+#endif
+ case 'R':
+ SET(RESTRICTED);
+ break;
+#ifndef NANO_TINY
+ case 'S':
+ SET(SMOOTH_SCROLL);
+ break;
+#endif
+ case 'T':
+ if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
+ fprintf(stderr, _("Requested tab size \"%s\" is invalid"), optarg);
+ fprintf(stderr, "\n");
+ exit(1);
+ }
+ break;
+#ifndef NANO_TINY
+ case 'U':
+ SET(QUICK_BLANK);
+ break;
+#endif
+ case 'V':
+ version();
+ exit(0);
+#ifndef NANO_TINY
+ case 'W':
+ SET(WORD_BOUNDS);
+ break;
+#endif
+#ifdef ENABLE_COLOR
+ case 'Y':
+ syntaxstr = mallocstrcpy(syntaxstr, optarg);
+ break;
+#endif
+ case 'c':
+ SET(CONST_UPDATE);
+ break;
+ case 'd':
+ SET(REBIND_DELETE);
+ break;
+#ifndef NANO_TINY
+ case 'i':
+ SET(AUTOINDENT);
+ break;
+ case 'k':
+ SET(CUT_TO_END);
+ break;
+#endif
+ case 'l':
+ SET(NOFOLLOW_SYMLINKS);
+ break;
+#ifndef DISABLE_MOUSE
+ case 'm':
+ SET(USE_MOUSE);
+ break;
+#endif
+#ifndef DISABLE_OPERATINGDIR
+ case 'o':
+ operating_dir = mallocstrcpy(operating_dir, optarg);
+ break;
+#endif
+ case 'p':
+ SET(PRESERVE);
+ break;
+ case 'q':
+ SET(QUIET);
+ break;
+#ifndef DISABLE_WRAPJUSTIFY
+ case 'r':
+ if (!parse_num(optarg, &wrap_at)) {
+ fprintf(stderr, _("Requested fill size \"%s\" is invalid"), optarg);
+ fprintf(stderr, "\n");
+ exit(1);
+ }
+ fill_used = TRUE;
+ break;
+#endif
+#ifndef DISABLE_SPELLER
+ case 's':
+ alt_speller = mallocstrcpy(alt_speller, optarg);
+ break;
+#endif
+ case 't':
+ SET(TEMP_FILE);
+ break;
+#ifndef NANO_TINY
+ case 'u':
+ SET(UNDOABLE);
+ break;
+#endif
+ case 'v':
+ SET(VIEW_MODE);
+ break;
+#ifndef DISABLE_WRAPPING
+ case 'w':
+ SET(NO_WRAP);
+
+ /* If both --fill and --nowrap are given on the command line,
+ the last option wins, */
+ fill_used = FALSE;
+
+ break;
+#endif
+ case 'x':
+ SET(NO_HELP);
+ break;
+ case 'z':
+ SET(SUSPEND);
+ break;
+#ifndef NANO_TINY
+ case '$':
+ SET(SOFTWRAP);
+ break;
+#endif
+ default:
+ usage();
+ }
+ }
+
+ /* If the executable filename starts with 'r', enable restricted
+ * mode. */
+ if (*(tail(argv[0])) == 'r')
+ SET(RESTRICTED);
+
+ /* If we're using restricted mode, disable suspending, backups, and
+ * reading rcfiles, since they all would allow reading from or
+ * writing to files not specified on the command line. */
+ if (ISSET(RESTRICTED)) {
+ UNSET(SUSPEND);
+ UNSET(BACKUP_FILE);
+#ifdef ENABLE_NANORC
+ no_rcfiles = TRUE;
+#endif
+ }
+
+
+ /* Set up the shortcut lists.
+ Need to do this before the rcfile */
+ shortcut_init(FALSE);
+
+/* We've read through the command line options. Now back up the flags
+ * and values that are set, and read the rcfile(s). If the values
+ * haven't changed afterward, restore the backed-up values. */
+#ifdef ENABLE_NANORC
+ if (!no_rcfiles) {
+#ifndef DISABLE_OPERATINGDIR
+ char *operating_dir_cpy = operating_dir;
+#endif
+#ifndef DISABLE_WRAPJUSTIFY
+ ssize_t wrap_at_cpy = wrap_at;
+#endif
+#ifndef NANO_TINY
+ char *backup_dir_cpy = backup_dir;
+#endif
+#ifndef DISABLE_JUSTIFY
+ char *quotestr_cpy = quotestr;
+#endif
+#ifndef DISABLE_SPELLER
+ char *alt_speller_cpy = alt_speller;
+#endif
+ ssize_t tabsize_cpy = tabsize;
+ unsigned flags_cpy[sizeof(flags) / sizeof(flags[0])];
+ size_t i;
+
+ memcpy(flags_cpy, flags, sizeof(flags_cpy));
+
+#ifndef DISABLE_OPERATINGDIR
+ operating_dir = NULL;
+#endif
+#ifndef NANO_TINY
+ backup_dir = NULL;
+#endif
+#ifndef DISABLE_JUSTIFY
+ quotestr = NULL;
+#endif
+#ifndef DISABLE_SPELLER
+ alt_speller = NULL;
+#endif
+
+ do_rcfile();
+
+#ifdef DEBUG
+ fprintf(stderr, "After rebinding keys...\n");
+ print_sclist();
+#endif
+
+#ifndef DISABLE_OPERATINGDIR
+ if (operating_dir_cpy != NULL) {
+ free(operating_dir);
+ operating_dir = operating_dir_cpy;
+ }
+#endif
+#ifndef DISABLE_WRAPJUSTIFY
+ if (fill_used)
+ wrap_at = wrap_at_cpy;
+#endif
+#ifndef NANO_TINY
+ if (backup_dir_cpy != NULL) {
+ free(backup_dir);
+ backup_dir = backup_dir_cpy;
+ }
+#endif
+#ifndef DISABLE_JUSTIFY
+ if (quotestr_cpy != NULL) {
+ free(quotestr);
+ quotestr = quotestr_cpy;
+ }
+#endif
+#ifndef DISABLE_SPELLER
+ if (alt_speller_cpy != NULL) {
+ free(alt_speller);
+ alt_speller = alt_speller_cpy;
+ }
+#endif
+ if (tabsize_cpy != -1)
+ tabsize = tabsize_cpy;
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++)
+ flags[i] |= flags_cpy[i];
+ }
+#ifdef DISABLE_ROOTWRAPPING
+ /* If we don't have any rcfiles, --disable-wrapping-as-root is used,
+ * and we're root, turn wrapping off. */
+ else if (geteuid() == NANO_ROOT_UID)
+ SET(NO_WRAP);
+#endif
+#endif /* ENABLE_NANORC */
+
+#ifndef DISABLE_WRAPPING
+ /* Overwrite an rcfile "set nowrap" or --disable-wrapping-as-root
+ if a --fill option was given on the command line. */
+ if (fill_used)
+ UNSET(NO_WRAP);
+#endif
+
+ /* If we're using bold text instead of reverse video text, set it up
+ * now. */
+ if (ISSET(BOLD_TEXT))
+ reverse_attr = A_BOLD;
+
+#ifndef NANO_TINY
+ /* Set up the search/replace history. */
+ history_init();
+#ifdef ENABLE_NANORC
+ if (!no_rcfiles) {
+ if (ISSET(HISTORYLOG) || ISSET(POS_HISTORY)) {
+ if (check_dotnano() == 0) {
+ UNSET(HISTORYLOG);
+ UNSET(POS_HISTORY);
+ }
+ }
+ if (ISSET(HISTORYLOG))
+ load_history();
+ if (ISSET(POS_HISTORY))
+ load_poshistory();
+ }
+#endif /* ENABLE_NANORC */
+#endif /* NANO_TINY */
+
+#ifndef NANO_TINY
+ /* Set up the backup directory (unless we're using restricted mode,
+ * in which case backups are disabled, since they would allow
+ * reading from or writing to files not specified on the command
+ * line). This entails making sure it exists and is a directory, so
+ * that backup files will be saved there. */
+ if (!ISSET(RESTRICTED))
+ init_backup_dir();
+#endif
+
+#ifndef DISABLE_OPERATINGDIR
+ /* Set up the operating directory. This entails chdir()ing there,
+ * so that file reads and writes will be based there. */
+ init_operating_dir();
+#endif
+
+#ifndef DISABLE_JUSTIFY
+ /* If punct wasn't specified, set its default value. */
+ if (punct == NULL)
+ punct = mallocstrcpy(NULL, "!.?");
+
+ /* If brackets wasn't specified, set its default value. */
+ if (brackets == NULL)
+ brackets = mallocstrcpy(NULL, "\"')>]}");
+
+ /* If quotestr wasn't specified, set its default value. */
+ if (quotestr == NULL)
+ quotestr = mallocstrcpy(NULL,
+#ifdef HAVE_REGEX_H
+ "^([ \t]*[#:>|}])+"
+#else
+ "> "
+#endif
+ );
+#ifdef HAVE_REGEX_H
+ quoterc = regcomp(&quotereg, quotestr, REG_EXTENDED);
+
+ if (quoterc == 0) {
+ /* We no longer need quotestr, just quotereg. */
+ free(quotestr);
+ quotestr = NULL;
+ } else {
+ size_t size = regerror(quoterc, &quotereg, NULL, 0);
+
+ quoteerr = charalloc(size);
+ regerror(quoterc, &quotereg, quoteerr, size);
+ }
+#else
+ quotelen = strlen(quotestr);
+#endif /* !HAVE_REGEX_H */
+#endif /* !DISABLE_JUSTIFY */
+
+#ifndef DISABLE_SPELLER
+ /* If we don't have an alternative spell checker after reading the
+ * command line and/or rcfile(s), check $SPELL for one, as Pico
+ * does (unless we're using restricted mode, in which case spell
+ * checking is disabled, since it would allow reading from or
+ * writing to files not specified on the command line). */
+ if (!ISSET(RESTRICTED) && alt_speller == NULL) {
+ char *spellenv = getenv("SPELL");
+ if (spellenv != NULL)
+ alt_speller = mallocstrcpy(NULL, spellenv);
+ }
+#endif
+
+#ifndef NANO_TINY
+ /* If matchbrackets wasn't specified, set its default value. */
+ if (matchbrackets == NULL)
+ matchbrackets = mallocstrcpy(NULL, "(<[{)>]}");
+#endif
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ /* If whitespace wasn't specified, set its default value. */
+ if (whitespace == NULL) {
+ whitespace = mallocstrcpy(NULL, " ");
+ whitespace_len[0] = 1;
+ whitespace_len[1] = 1;
+ }
+#endif
+
+ /* If tabsize wasn't specified, set its default value. */
+ if (tabsize == -1)
+ tabsize = WIDTH_OF_TAB;
+
+ /* Back up the old terminal settings so that they can be restored. */
+ tcgetattr(0, &oldterm);
+
+ /* Initialize curses mode. If this fails, get out. */
+ if (initscr() == NULL)
+ exit(1);
+
+ /* Set up the terminal state. */
+ terminal_init();
+
+ /* Turn the cursor on for sure. */
+ curs_set(1);
+
+#ifdef DEBUG
+ fprintf(stderr, "Main: set up windows\n");
+#endif
+
+ /* Initialize all the windows based on the current screen
+ * dimensions. */
+ window_init();
+
+ /* Set up the signal handlers. */
+ signal_init();
+
+#ifndef DISABLE_MOUSE
+ /* Initialize mouse support. */
+ mouse_init();
+#endif
+
+#ifdef DEBUG
+ fprintf(stderr, "Main: open file\n");
+#endif
+
+ /* If there's a +LINE or +LINE,COLUMN flag here, it is the first
+ * non-option argument, and it is followed by at least one other
+ * argument, the filename it applies to. */
+ if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
+ parse_line_column(&argv[optind][1], &startline, &startcol);
+ optind++;
+ }
+
+ if (optind < argc && !strcmp(argv[optind], "-")) {
+ stdin_pager();
+ set_modified();
+ optind++;
+ }
+
+#ifdef ENABLE_MULTIBUFFER
+ old_multibuffer = ISSET(MULTIBUFFER);
+ SET(MULTIBUFFER);
+
+ /* Read all the files after the first one on the command line into
+ * new buffers. */
+ {
+ int i = optind + 1;
+ ssize_t iline = 1, icol = 1;
+
+ for (; i < argc; i++) {
+ /* If there's a +LINE or +LINE,COLUMN flag here, it is
+ * followed by at least one other argument, the filename it
+ * applies to. */
+ if (i < argc - 1 && argv[i][0] == '+' && iline == 1 &&
+ icol == 1)
+ parse_line_column(&argv[i][1], &iline, &icol);
+ else {
+ open_buffer(argv[i], FALSE);
+
+ if (iline > 1 || icol > 1) {
+ do_gotolinecolumn(iline, icol, FALSE, FALSE, FALSE,
+ FALSE);
+ iline = 1;
+ icol = 1;
+ }
+#ifndef NANO_TINY
+ else {
+ /* See if we have a POS history to use if we haven't overridden it */
+ ssize_t savedposline, savedposcol;
+ if (check_poshistory(argv[i], &savedposline, &savedposcol))
+ do_gotolinecolumn(savedposline, savedposcol, FALSE, FALSE, FALSE,
+ FALSE);
+ }
+#endif /* NANO_TINY */
+ }
+ }
+ }
+#endif
+
+ /* Read the first file on the command line into either the current
+ * buffer or a new buffer, depending on whether multibuffer mode is
+ * enabled. */
+ if (optind < argc)
+ open_buffer(argv[optind], FALSE);
+
+ /* We didn't open any files if all the command line arguments were
+ * invalid files like directories or if there were no command line
+ * arguments given. In this case, we have to load a blank buffer.
+ * Also, we unset view mode to allow editing. */
+ if (openfile == NULL) {
+ open_buffer("", FALSE);
+ UNSET(VIEW_MODE);
+ }
+
+#ifdef ENABLE_MULTIBUFFER
+ if (!old_multibuffer)
+ UNSET(MULTIBUFFER);
+#endif
+
+#ifdef DEBUG
+ fprintf(stderr, "Main: top and bottom win\n");
+#endif
+
+#ifdef ENABLE_COLOR
+ if (openfile->syntax && openfile->syntax->nmultis > 0)
+ precalc_multicolorinfo();
+#endif
+
+ if (startline > 1 || startcol > 1)
+ do_gotolinecolumn(startline, startcol, FALSE, FALSE, FALSE,
+ FALSE);
+# ifndef NANO_TINY
+ else {
+ /* See if we have a POS history to use if we haven't overridden it */
+ ssize_t savedposline, savedposcol;
+ if (check_poshistory(argv[optind], &savedposline, &savedposcol))
+ do_gotolinecolumn(savedposline, savedposcol, FALSE, FALSE, FALSE, FALSE);
+ }
+#endif /* NANO_TINY */
+
+ display_main_list();
+
+ display_buffer();
+
+ while (TRUE) {
+ bool meta_key, func_key, s_or_t, ran_func, finished;
+
+ /* Make sure the cursor is in the edit window. */
+ reset_cursor();
+ wnoutrefresh(edit);
+
+#ifndef NANO_TINY
+ if (!jump_buf_main) {
+ /* If we haven't already, we're going to set jump_buf so
+ * that we return here after a SIGWINCH. Indicate this. */
+ jump_buf_main = TRUE;
+
+ /* Return here after a SIGWINCH. */
+ sigsetjmp(jump_buf, 1);
+ }
+#endif
+
+ /* Just in case we were at the statusbar prompt, make sure the
+ * statusbar cursor position is reset. */
+ do_prompt_abort();
+
+ /* If constant cursor position display is on, and there are no
+ * keys waiting in the input buffer, display the current cursor
+ * position on the statusbar. */
+ if (ISSET(CONST_UPDATE) && get_key_buffer_len() == 0)
+ do_cursorpos(TRUE);
+
+ currmenu = MMAIN;
+
+ /* Read in and interpret characters. */
+ do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
+ TRUE);
+ }
+
+ /* We should never get here. */
+ assert(FALSE);
+}
+
diff --git a/src/nano.h b/src/nano.h
new file mode 100644
index 0000000..b19d195
--- /dev/null
+++ b/src/nano.h
@@ -0,0 +1,794 @@
+/* $Id: nano.h 4535 2011-02-26 14:22:37Z astyanax $ */
+/**************************************************************************
+ * nano.h *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#ifndef NANO_H
+#define NANO_H 1
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef NEED_XOPEN_SOURCE_EXTENDED
+#ifndef _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_SOURCE_EXTENDED 1
+#endif /* _XOPEN_SOURCE_EXTENDED */
+#endif /* NEED_XOPEN_SOURCE_EXTENDED */
+
+#ifdef __TANDEM
+/* Tandem NonStop Kernel support. */
+#include <floss.h>
+#define NANO_ROOT_UID 65535
+#else
+#define NANO_ROOT_UID 0
+#endif
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#ifdef HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+
+/* Suppress warnings for __attribute__((warn_unused_result)) */
+#define IGNORE_CALL_RESULT(call) do { if (call) {} } while(0)
+
+/* Macros for flags. */
+#define FLAGOFF(flag) ((flag) / (sizeof(unsigned) * 8))
+#define FLAGMASK(flag) (1 << ((flag) % (sizeof(unsigned) * 8)))
+#define FLAGS(flag) flags[FLAGOFF(flag)]
+#define SET(flag) FLAGS(flag) |= FLAGMASK(flag)
+#define UNSET(flag) FLAGS(flag) &= ~FLAGMASK(flag)
+#define ISSET(flag) ((FLAGS(flag) & FLAGMASK(flag)) != 0)
+#define TOGGLE(flag) FLAGS(flag) ^= FLAGMASK(flag)
+
+/* Macros for character allocation and more. */
+#define charalloc(howmuch) (char *)nmalloc((howmuch) * sizeof(char))
+#define charealloc(ptr, howmuch) (char *)nrealloc(ptr, (howmuch) * sizeof(char))
+#define charmove(dest, src, n) memmove(dest, src, (n) * sizeof(char))
+#define charset(dest, src, n) memset(dest, src, (n) * sizeof(char))
+
+/* Set a default value for PATH_MAX if there isn't one. */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#ifdef USE_SLANG
+/* Slang support. */
+#include <slcurses.h>
+/* Slang curses emulation brain damage, part 3: Slang doesn't define the
+ * curses equivalents of the Insert or Delete keys. */
+#define KEY_DC SL_KEY_DELETE
+#define KEY_IC SL_KEY_IC
+/* Ncurses support. */
+#elif defined(HAVE_NCURSES_H)
+#include <ncurses.h>
+#else
+/* Curses support. */
+#include <curses.h>
+#endif /* CURSES_H */
+
+#ifdef ENABLE_NLS
+/* Native language support. */
+#ifdef HAVE_LIBINTL_H
+#include <libintl.h>
+#endif
+#define _(string) gettext(string)
+#define P_(singular, plural, number) ngettext(singular, plural, number)
+#else
+#define _(string) (string)
+#define P_(singular, plural, number) (number == 1 ? singular : plural)
+#endif
+#define gettext_noop(string) (string)
+#define N_(string) gettext_noop(string)
+ /* Mark a string that will be sent to gettext() later. */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+#ifndef NANO_TINY
+#include <setjmp.h>
+#endif
+#include <assert.h>
+
+/* If no vsnprintf(), use the version from glib 2.x. */
+#ifndef HAVE_VSNPRINTF
+#include <glib.h>
+#define vsnprintf g_vsnprintf
+#endif
+
+/* If no isblank(), iswblank(), strcasecmp(), strncasecmp(),
+ * strcasestr(), strnlen(), getdelim(), or getline(), use the versions
+ * we have. */
+#ifndef HAVE_ISBLANK
+#define isblank nisblank
+#endif
+#ifndef HAVE_ISWBLANK
+#define iswblank niswblank
+#endif
+#ifndef HAVE_STRCASECMP
+#define strcasecmp nstrcasecmp
+#endif
+#ifndef HAVE_STRNCASECMP
+#define strncasecmp nstrncasecmp
+#endif
+#ifndef HAVE_STRCASESTR
+#define strcasestr nstrcasestr
+#endif
+#ifndef HAVE_STRNLEN
+#define strnlen nstrnlen
+#endif
+#ifndef HAVE_GETDELIM
+#define getdelim ngetdelim
+#endif
+#ifndef HAVE_GETLINE
+#define getline ngetline
+#endif
+
+/* If we aren't using ncurses with mouse support, turn the mouse support
+ * off, as it's useless then. */
+#ifndef NCURSES_MOUSE_VERSION
+#define DISABLE_MOUSE 1
+#endif
+
+#if defined(DISABLE_WRAPPING) && defined(DISABLE_JUSTIFY)
+#define DISABLE_WRAPJUSTIFY 1
+#endif
+
+/* Enumeration types. */
+typedef enum {
+ NIX_FILE, DOS_FILE, MAC_FILE
+} file_format;
+
+typedef enum {
+ OVERWRITE, APPEND, PREPEND
+} append_type;
+
+typedef enum {
+ UP_DIR, DOWN_DIR
+} scroll_dir;
+
+typedef enum {
+ CENTER, NONE
+} update_type;
+
+typedef enum {
+ CONTROL, META, FKEY, RAWINPUT
+} function_type;
+
+typedef enum {
+ ADD, DEL, REPLACE, SPLIT, UNSPLIT, CUT, UNCUT, ENTER, INSERT, OTHER
+} undo_type;
+
+#ifdef ENABLE_COLOR
+typedef struct colortype {
+ short fg;
+ /* This syntax's foreground color. */
+ short bg;
+ /* This syntax's background color. */
+ bool bright;
+ /* Is this color A_BOLD? */
+ bool icase;
+ /* Is this regex string case insensitive? */
+ int pairnum;
+ /* The color pair number used for this foreground color and
+ * background color. */
+ char *start_regex;
+ /* The start (or all) of the regex string. */
+ regex_t *start;
+ /* The compiled start (or all) of the regex string. */
+ char *end_regex;
+ /* The end (if any) of the regex string. */
+ regex_t *end;
+ /* The compiled end (if any) of the regex string. */
+ struct colortype *next;
+ /* Next set of colors. */
+ int id;
+ /* basic id for assigning to lines later */
+} colortype;
+
+typedef struct exttype {
+ char *ext_regex;
+ /* The extensions that match this syntax. */
+ regex_t *ext;
+ /* The compiled extensions that match this syntax. */
+ struct exttype *next;
+ /* Next set of extensions. */
+} exttype;
+
+typedef struct syntaxtype {
+ char *desc;
+ /* The name of this syntax. */
+ exttype *extensions;
+ /* The list of extensions that this syntax applies to. */
+ exttype *headers;
+ /* Regexes to match on the 'header' (1st line) of the file */
+ exttype *magics;
+ /* Regexes to match libmagic results */
+ colortype *color;
+ /* The colors used in this syntax. */
+ int nmultis;
+ /* How many multi line strings this syntax has */
+ struct syntaxtype *next;
+ /* Next syntax. */
+} syntaxtype;
+
+#define CNONE (1<<1)
+ /* Yay, regex doesn't apply to this line at all! */
+#define CBEGINBEFORE (1<<2)
+ /* regex starts on an earlier line, ends on this one */
+#define CENDAFTER (1<<3)
+ /* regex sraers on this line and ends on a later one */
+#define CWHOLELINE (1<<4)
+ /* whole line engulfed by the regex start < me, end > me */
+#define CSTARTENDHERE (1<<5)
+ /* regex starts and ends within this line */
+#define CWTF (1<<6)
+ /* Something else */
+
+#endif /* ENABLE_COLOR */
+
+
+/* Structure types. */
+typedef struct filestruct {
+ char *data;
+ /* The text of this line. */
+ ssize_t lineno;
+ /* The number of this line. */
+ struct filestruct *next;
+ /* Next node. */
+ struct filestruct *prev;
+ /* Previous node. */
+#ifdef ENABLE_COLOR
+ short *multidata; /* Array of which multi-line regexes apply to this line */
+#endif
+} filestruct;
+
+typedef struct partition {
+ filestruct *fileage;
+ /* The top line of this portion of the file. */
+ filestruct *top_prev;
+ /* The line before the top line of this portion of the file. */
+ char *top_data;
+ /* The text before the beginning of the top line of this portion
+ * of the file. */
+ filestruct *filebot;
+ /* The bottom line of this portion of the file. */
+ filestruct *bot_next;
+ /* The line after the bottom line of this portion of the
+ * file. */
+ char *bot_data;
+ /* The text after the end of the bottom line of this portion of
+ * the file. */
+} partition;
+
+#ifndef NANO_TINY
+typedef struct undo {
+ ssize_t lineno;
+ undo_type type;
+ /* What type of undo was this */
+ int begin;
+ /* Where did this action begin or end */
+ char *strdata;
+ /* String type data we will use for ccopying the affected line back */
+ char *strdata2;
+ /* Sigh, need this too it looks like */
+ int xflags;
+ /* Some flag data we need */
+
+ /* Cut specific stuff we need */
+ filestruct *cutbuffer;
+ /* Copy of the cutbuffer */
+ filestruct *cutbottom;
+ /* Copy of cutbottom */
+ bool mark_set;
+ /* was the marker set when we cut */
+ bool to_end;
+ /* was this a cut to end */
+ ssize_t mark_begin_lineno;
+ /* copy copy copy */
+ ssize_t mark_begin_x;
+ /* Another shadow variable */
+ struct undo *next;
+} undo;
+
+
+typedef struct poshiststruct {
+ char *filename;
+ /* The file. */
+ ssize_t lineno;
+ /* Line number we left off on */
+ ssize_t xno;
+ /* x position in the file we left off on */
+ struct poshiststruct *next;
+} poshiststruct;
+#endif /* NANO_TINY */
+
+
+typedef struct openfilestruct {
+ char *filename;
+ /* The current file's name. */
+ filestruct *fileage;
+ /* The current file's first line. */
+ filestruct *filebot;
+ /* The current file's last line. */
+ filestruct *edittop;
+ /* The current top of the edit window. */
+ filestruct *current;
+ /* The current file's current line. */
+ size_t totsize;
+ /* The current file's total number of characters. */
+ size_t current_x;
+ /* The current file's x-coordinate position. */
+ size_t placewewant;
+ /* The current file's place we want. */
+ ssize_t current_y;
+ /* The current file's y-coordinate position. */
+ bool modified;
+ /* Whether the current file has been modified. */
+#ifndef NANO_TINY
+ bool mark_set;
+ /* Whether the mark is on in the current file. */
+ filestruct *mark_begin;
+ /* The current file's beginning marked line, if any. */
+ size_t mark_begin_x;
+ /* The current file's beginning marked line's x-coordinate
+ * position, if any. */
+ file_format fmt;
+ /* The current file's format. */
+ struct stat *current_stat;
+ /* The current file's stat. */
+ undo *undotop;
+ /* Top of the undo list */
+ undo *current_undo;
+ /* The current (i.e. n ext) level of undo */
+ undo_type last_action;
+#endif
+#ifdef ENABLE_COLOR
+ syntaxtype *syntax;
+ /* The syntax struct for this file, if any */
+ colortype *colorstrings;
+ /* The current file's associated colors. */
+#endif
+ struct openfilestruct *next;
+ /* Next node. */
+ struct openfilestruct *prev;
+ /* Previous node. */
+} openfilestruct;
+
+typedef struct shortcut {
+ const char *desc;
+ /* The function's description, e.g. "Page Up". */
+#ifndef DISABLE_HELP
+ const char *help;
+ /* The help file entry text for this function. */
+ bool blank_after;
+ /* Whether there should be a blank line after the help entry
+ * text for this function. */
+#endif
+ /* Note: Key values that aren't used should be set to
+ * NANO_NO_KEY. */
+ int ctrlval;
+ /* The special sentinel key or control key we want bound, if
+ * any. */
+ int metaval;
+ /* The meta key we want bound, if any. */
+ int funcval;
+ /* The function key we want bound, if any. */
+ int miscval;
+ /* The other meta key we want bound, if any. */
+ bool viewok;
+ /* Is this function allowed when in view mode? */
+ void (*func)(void);
+ /* The function to call when we get this key. */
+ struct shortcut *next;
+ /* Next shortcut. */
+} shortcut;
+
+#ifdef ENABLE_NANORC
+typedef struct rcoption {
+ const char *name;
+ /* The name of the rcfile option. */
+ long flag;
+ /* The flag associated with it, if any. */
+} rcoption;
+
+#endif
+
+typedef struct sc {
+ char *keystr;
+ /* The shortcut key for a function, ASCII version */
+ function_type type;
+ /* What kind of function key is it for convenience later */
+ int seq;
+ /* The actual sequence to check on the the type is determined */
+ int menu;
+ /* What list does this apply to */
+ void (*scfunc)(void);
+ /* The function we're going to run */
+ int toggle;
+ /* If a toggle, what we're toggling */
+ bool execute;
+ /* Whether to execute the function in question or just return
+ so the sequence can be caught by the calling code */
+ struct sc *next;
+ /* Next in the list */
+} sc;
+
+typedef struct subnfunc {
+ void (*scfunc)(void);
+ /* What function is this */
+ int menus;
+ /* In what menus does this function applu */
+ const char *desc;
+ /* The function's description, e.g. "Page Up". */
+#ifndef DISABLE_HELP
+ const char *help;
+ /* The help file entry text for this function. */
+ bool blank_after;
+ /* Whether there should be a blank line after the help entry
+ * text for this function. */
+#endif
+ bool viewok;
+ /* Is this function allowed when in view mode? */
+ long toggle;
+ /* If this is a toggle, if nonzero what toggle to set */
+ struct subnfunc *next;
+ /* next item in the list */
+} subnfunc;
+
+
+/* Enumeration to be used in flags table. See FLAGBIT and FLAGOFF
+ * definitions. */
+enum
+{
+ DONTUSE,
+ CASE_SENSITIVE,
+ CONST_UPDATE,
+ NO_HELP,
+ NOFOLLOW_SYMLINKS,
+ SUSPEND,
+ NO_WRAP,
+ AUTOINDENT,
+ VIEW_MODE,
+ USE_MOUSE,
+ USE_REGEXP,
+ TEMP_FILE,
+ CUT_TO_END,
+ BACKWARDS_SEARCH,
+ MULTIBUFFER,
+ SMOOTH_SCROLL,
+ REBIND_DELETE,
+ REBIND_KEYPAD,
+ NO_CONVERT,
+ BACKUP_FILE,
+ INSECURE_BACKUP,
+ NO_COLOR_SYNTAX,
+ PRESERVE,
+ HISTORYLOG,
+ RESTRICTED,
+ SMART_HOME,
+ WHITESPACE_DISPLAY,
+ MORE_SPACE,
+ TABS_TO_SPACES,
+ QUICK_BLANK,
+ WORD_BOUNDS,
+ NO_NEWLINES,
+ BOLD_TEXT,
+ QUIET,
+ UNDOABLE,
+ SOFTWRAP,
+ POS_HISTORY
+};
+
+/* Flags for which menus in which a given function should be present */
+#define MMAIN (1<<0)
+#define MWHEREIS (1<<1)
+#define MREPLACE (1<<2)
+#define MREPLACE2 (1<<3)
+#define MGOTOLINE (1<<4)
+#define MWRITEFILE (1<<5)
+#define MINSERTFILE (1<<6)
+#define MEXTCMD (1<<7)
+#define MHELP (1<<8)
+#define MSPELL (1<<9)
+#define MBROWSER (1<<10)
+#define MWHEREISFILE (1<<11)
+#define MGOTODIR (1<<12)
+#define MYESNO (1<<13)
+/* This really isnt all but close enough */
+#define MALL (MMAIN|MWHEREIS|MREPLACE|MREPLACE2|MGOTOLINE|MWRITEFILE|MINSERTFILE|MEXTCMD|MSPELL|MBROWSER|MWHEREISFILE|MGOTODIR|MHELP)
+
+/* Control key sequences. Changing these would be very, very bad. */
+#define NANO_CONTROL_SPACE 0
+#define NANO_CONTROL_A 1
+#define NANO_CONTROL_B 2
+#define NANO_CONTROL_C 3
+#define NANO_CONTROL_D 4
+#define NANO_CONTROL_E 5
+#define NANO_CONTROL_F 6
+#define NANO_CONTROL_G 7
+#define NANO_CONTROL_H 8
+#define NANO_CONTROL_I 9
+#define NANO_CONTROL_J 10
+#define NANO_CONTROL_K 11
+#define NANO_CONTROL_L 12
+#define NANO_CONTROL_M 13
+#define NANO_CONTROL_N 14
+#define NANO_CONTROL_O 15
+#define NANO_CONTROL_P 16
+#define NANO_CONTROL_Q 17
+#define NANO_CONTROL_R 18
+#define NANO_CONTROL_S 19
+#define NANO_CONTROL_T 20
+#define NANO_CONTROL_U 21
+#define NANO_CONTROL_V 22
+#define NANO_CONTROL_W 23
+#define NANO_CONTROL_X 24
+#define NANO_CONTROL_Y 25
+#define NANO_CONTROL_Z 26
+#define NANO_CONTROL_3 27
+#define NANO_CONTROL_4 28
+#define NANO_CONTROL_5 29
+#define NANO_CONTROL_6 30
+#define NANO_CONTROL_7 31
+#define NANO_CONTROL_8 127
+
+/* Meta key sequences. */
+#define NANO_META_SPACE ' '
+#define NANO_META_LPARENTHESIS '('
+#define NANO_META_RPARENTHESIS ')'
+#define NANO_META_PLUS '+'
+#define NANO_META_COMMA ','
+#define NANO_META_MINUS '-'
+#define NANO_META_PERIOD '.'
+#define NANO_META_SLASH '/'
+#define NANO_META_0 '0'
+#define NANO_META_6 '6'
+#define NANO_META_9 '9'
+#define NANO_META_LCARET '<'
+#define NANO_META_EQUALS '='
+#define NANO_META_RCARET '>'
+#define NANO_META_QUESTION '?'
+#define NANO_META_BACKSLASH '\\'
+#define NANO_META_RBRACKET ']'
+#define NANO_META_CARET '^'
+#define NANO_META_UNDERSCORE '_'
+#define NANO_META_A 'a'
+#define NANO_META_B 'b'
+#define NANO_META_C 'c'
+#define NANO_META_D 'd'
+#define NANO_META_E 'e'
+#define NANO_META_F 'f'
+#define NANO_META_G 'g'
+#define NANO_META_H 'h'
+#define NANO_META_I 'i'
+#define NANO_META_J 'j'
+#define NANO_META_K 'k'
+#define NANO_META_L 'l'
+#define NANO_META_M 'm'
+#define NANO_META_N 'n'
+#define NANO_META_O 'o'
+#define NANO_META_P 'p'
+#define NANO_META_Q 'q'
+#define NANO_META_R 'r'
+#define NANO_META_S 's'
+#define NANO_META_T 't'
+#define NANO_META_U 'u'
+#define NANO_META_V 'v'
+#define NANO_META_W 'w'
+#define NANO_META_X 'x'
+#define NANO_META_Y 'y'
+#define NANO_META_Z 'z'
+#define NANO_META_LCURLYBRACKET '{'
+#define NANO_META_PIPE '|'
+#define NANO_META_RCURLYBRACKET '}'
+
+/* Some semi-changeable keybindings; don't play with these unless you're
+ * sure you know what you're doing. Assume ERR is defined as -1. */
+
+/* No key at all. */
+#define NANO_NO_KEY -2
+
+/* Normal keys. */
+#define NANO_XON_KEY NANO_CONTROL_Q
+#define NANO_XOFF_KEY NANO_CONTROL_S
+#define NANO_CANCEL_KEY NANO_CONTROL_C
+#define NANO_EXIT_KEY NANO_CONTROL_X
+#define NANO_EXIT_FKEY KEY_F(2)
+#define NANO_INSERTFILE_KEY NANO_CONTROL_R
+#define NANO_INSERTFILE_FKEY KEY_F(5)
+#define NANO_TOOTHERINSERT_KEY NANO_CONTROL_X
+#define NANO_WRITEOUT_KEY NANO_CONTROL_O
+#define NANO_WRITEOUT_FKEY KEY_F(3)
+#define NANO_GOTOLINE_KEY NANO_CONTROL_7
+#define NANO_GOTOLINE_FKEY KEY_F(13)
+#define NANO_GOTOLINE_METAKEY NANO_META_G
+#define NANO_GOTODIR_KEY NANO_CONTROL_7
+#define NANO_GOTODIR_FKEY KEY_F(13)
+#define NANO_GOTODIR_METAKEY NANO_META_G
+#define NANO_TOGOTOLINE_KEY NANO_CONTROL_T
+#define NANO_HELP_KEY NANO_CONTROL_G
+#define NANO_HELP_FKEY KEY_F(1)
+#define NANO_WHEREIS_KEY NANO_CONTROL_W
+#define NANO_WHEREIS_FKEY KEY_F(6)
+#define NANO_WHEREIS_NEXT_KEY NANO_META_W
+#define NANO_WHEREIS_NEXT_FKEY KEY_F(16)
+#define NANO_TOOTHERWHEREIS_KEY NANO_CONTROL_T
+#define NANO_REGEXP_KEY NANO_META_R
+#define NANO_REPLACE_KEY NANO_CONTROL_4
+#define NANO_REPLACE_FKEY KEY_F(14)
+#define NANO_REPLACE_METAKEY NANO_META_R
+#define NANO_TOOTHERSEARCH_KEY NANO_CONTROL_R
+#define NANO_PREVPAGE_KEY NANO_CONTROL_Y
+#define NANO_PREVPAGE_FKEY KEY_F(7)
+#define NANO_NEXTPAGE_KEY NANO_CONTROL_V
+#define NANO_NEXTPAGE_FKEY KEY_F(8)
+#define NANO_CUT_KEY NANO_CONTROL_K
+#define NANO_CUT_FKEY KEY_F(9)
+#define NANO_COPY_KEY NANO_META_CARET
+#define NANO_COPY_METAKEY NANO_META_6
+#define NANO_UNCUT_KEY NANO_CONTROL_U
+#define NANO_UNCUT_FKEY KEY_F(10)
+#define NANO_CURSORPOS_KEY NANO_CONTROL_C
+#define NANO_CURSORPOS_FKEY KEY_F(11)
+#define NANO_SPELL_KEY NANO_CONTROL_T
+#define NANO_SPELL_FKEY KEY_F(12)
+#define NANO_FIRSTLINE_KEY NANO_PREVPAGE_KEY
+#define NANO_FIRSTLINE_FKEY NANO_PREVPAGE_FKEY
+#define NANO_FIRSTLINE_METAKEY NANO_META_BACKSLASH
+#define NANO_FIRSTLINE_METAKEY2 NANO_META_PIPE
+#define NANO_FIRSTFILE_KEY NANO_FIRSTLINE_KEY
+#define NANO_FIRSTFILE_FKEY NANO_FIRSTLINE_FKEY
+#define NANO_FIRSTFILE_METAKEY NANO_FIRSTLINE_METAKEY
+#define NANO_FIRSTFILE_METAKEY2 NANO_FIRSTLINE_METAKEY2
+#define NANO_LASTLINE_KEY NANO_NEXTPAGE_KEY
+#define NANO_LASTLINE_FKEY NANO_NEXTPAGE_FKEY
+#define NANO_LASTLINE_METAKEY NANO_META_SLASH
+#define NANO_LASTLINE_METAKEY2 NANO_META_QUESTION
+#define NANO_LASTFILE_KEY NANO_LASTLINE_KEY
+#define NANO_LASTFILE_FKEY NANO_LASTLINE_FKEY
+#define NANO_LASTFILE_METAKEY NANO_LASTLINE_METAKEY
+#define NANO_LASTFILE_METAKEY2 NANO_LASTLINE_METAKEY2
+#define NANO_REFRESH_KEY NANO_CONTROL_L
+#define NANO_JUSTIFY_KEY NANO_CONTROL_J
+#define NANO_JUSTIFY_FKEY KEY_F(4)
+#define NANO_UNJUSTIFY_KEY NANO_UNCUT_KEY
+#define NANO_UNJUSTIFY_FKEY NANO_UNCUT_FKEY
+#define NANO_PREVLINE_KEY NANO_CONTROL_P
+#define NANO_NEXTLINE_KEY NANO_CONTROL_N
+#define NANO_FORWARD_KEY NANO_CONTROL_F
+#define NANO_BACK_KEY NANO_CONTROL_B
+#define NANO_MARK_KEY NANO_CONTROL_6
+#define NANO_MARK_METAKEY NANO_META_A
+#define NANO_MARK_FKEY KEY_F(15)
+#define NANO_HOME_KEY NANO_CONTROL_A
+#define NANO_END_KEY NANO_CONTROL_E
+#define NANO_DELETE_KEY NANO_CONTROL_D
+#define NANO_BACKSPACE_KEY NANO_CONTROL_H
+#define NANO_TAB_KEY NANO_CONTROL_I
+#define NANO_INDENT_KEY NANO_META_RCURLYBRACKET
+#define NANO_UNINDENT_KEY NANO_META_LCURLYBRACKET
+#define NANO_SUSPEND_KEY NANO_CONTROL_Z
+#define NANO_ENTER_KEY NANO_CONTROL_M
+#define NANO_TOFILES_KEY NANO_CONTROL_T
+#define NANO_APPEND_KEY NANO_META_A
+#define NANO_PREPEND_KEY NANO_META_P
+#define NANO_PREVFILE_KEY NANO_META_LCARET
+#define NANO_PREVFILE_METAKEY NANO_META_COMMA
+#define NANO_NEXTFILE_KEY NANO_META_RCARET
+#define NANO_NEXTFILE_METAKEY NANO_META_PERIOD
+#define NANO_BRACKET_KEY NANO_META_RBRACKET
+#define NANO_NEXTWORD_KEY NANO_CONTROL_SPACE
+#define NANO_PREVWORD_KEY NANO_META_SPACE
+#define NANO_WORDCOUNT_KEY NANO_META_D
+#define NANO_SCROLLUP_KEY NANO_META_MINUS
+#define NANO_SCROLLDOWN_KEY NANO_META_PLUS
+#define NANO_SCROLLUP_METAKEY NANO_META_UNDERSCORE
+#define NANO_SCROLLDOWN_METAKEY NANO_META_EQUALS
+#define NANO_CUTTILLEND_METAKEY NANO_META_T
+#define NANO_PARABEGIN_KEY NANO_CONTROL_W
+#define NANO_PARABEGIN_METAKEY NANO_META_LPARENTHESIS
+#define NANO_PARABEGIN_METAKEY2 NANO_META_9
+#define NANO_PARAEND_KEY NANO_CONTROL_O
+#define NANO_PARAEND_METAKEY NANO_META_RPARENTHESIS
+#define NANO_PARAEND_METAKEY2 NANO_META_0
+#define NANO_FULLJUSTIFY_KEY NANO_CONTROL_U
+#define NANO_FULLJUSTIFY_METAKEY NANO_META_J
+#define NANO_VERBATIM_KEY NANO_META_V
+
+/* Toggles do not exist if NANO_TINY is defined. */
+#ifndef NANO_TINY
+
+/* No toggle at all. */
+#define TOGGLE_NO_KEY -2
+
+/* Normal toggles. */
+#define TOGGLE_NOHELP_KEY NANO_META_X
+#define TOGGLE_CONST_KEY NANO_META_C
+#define TOGGLE_MORESPACE_KEY NANO_META_O
+#define TOGGLE_SMOOTH_KEY NANO_META_S
+#define TOGGLE_WHITESPACE_KEY NANO_META_P
+#define TOGGLE_SYNTAX_KEY NANO_META_Y
+#define TOGGLE_SMARTHOME_KEY NANO_META_H
+#define TOGGLE_AUTOINDENT_KEY NANO_META_I
+#define TOGGLE_CUTTOEND_KEY NANO_META_K
+#define TOGGLE_WRAP_KEY NANO_META_L
+#define TOGGLE_TABSTOSPACES_KEY NANO_META_Q
+#define TOGGLE_BACKUP_KEY NANO_META_B
+#define TOGGLE_MULTIBUFFER_KEY NANO_META_F
+#define TOGGLE_MOUSE_KEY NANO_META_M
+#define TOGGLE_NOCONVERT_KEY NANO_META_N
+#define TOGGLE_SUSPEND_KEY NANO_META_Z
+#define TOGGLE_CASE_KEY NANO_META_C
+#define TOGGLE_BACKWARDS_KEY NANO_META_B
+#define TOGGLE_DOS_KEY NANO_META_D
+#define TOGGLE_MAC_KEY NANO_META_M
+
+/* Extra bits for the undo function */
+#define UNdel_del (1<<0)
+#define UNdel_backspace (1<<1)
+#define UNsplit_madenew (1<<2)
+
+/* Since in ISO C you can't pass around function pointers anymore,
+ let's make some integer macros for function names, and then I
+ can go cut my wrists after writing the big switch statement
+ that will necessitate. */
+
+#endif /* !NANO_TINY */
+
+#define VIEW TRUE
+#define NOVIEW FALSE
+
+/* The maximum number of entries displayed in the main shortcut list. */
+#define MAIN_VISIBLE 12
+
+/* The minimum editor window columns and rows required for nano to work
+ * correctly. */
+#define MIN_EDITOR_COLS 4
+#define MIN_EDITOR_ROWS 1
+
+/* The default number of characters from the end of the line where
+ * wrapping occurs. */
+#define CHARS_FROM_EOL 8
+
+/* The default width of a tab in spaces. */
+#define WIDTH_OF_TAB 8
+
+/* The maximum number of search/replace history strings saved, not
+ * counting the blank lines at their ends. */
+#define MAX_SEARCH_HISTORY 100
+
+/* The maximum number of bytes buffered at one time. */
+#define MAX_BUF_SIZE 128
+
+#endif /* !NANO_H */
diff --git a/src/prompt.c b/src/prompt.c
new file mode 100644
index 0000000..0be25f5
--- /dev/null
+++ b/src/prompt.c
@@ -0,0 +1,1363 @@
+/* $Id: prompt.c 4527 2011-02-07 14:45:56Z astyanax $ */
+/**************************************************************************
+ * prompt.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+static char *prompt = NULL;
+ /* The prompt string used for statusbar questions. */
+static size_t statusbar_x = (size_t)-1;
+ /* The cursor position in answer. */
+static size_t statusbar_pww = (size_t)-1;
+ /* The place we want in answer. */
+static size_t old_statusbar_x = (size_t)-1;
+ /* The old cursor position in answer, if any. */
+static size_t old_pww = (size_t)-1;
+ /* The old place we want in answer, if any. */
+static bool reset_statusbar_x = FALSE;
+ /* Should we reset the cursor position at the statusbar
+ * prompt? */
+
+/* Read in a character, interpret it as a shortcut or toggle if
+ * necessary, and return it. Set meta_key to TRUE if the character is a
+ * meta sequence, set func_key to TRUE if the character is a function
+ * key, set have_shortcut to TRUE if the character is a shortcut
+ * key, set ran_func to TRUE if we ran a function associated with a
+ * shortcut key, and set finished to TRUE if we're done after running
+ * or trying to run a function associated with a shortcut key. If
+ * allow_funcs is FALSE, don't actually run any functions associated
+ * with shortcut keys. refresh_func is the function we will call to
+ * refresh the edit window. */
+int do_statusbar_input(bool *meta_key, bool *func_key, bool *have_shortcut,
+ bool *ran_func, bool *finished, bool allow_funcs, void
+ (*refresh_func)(void))
+{
+ int input;
+ /* The character we read in. */
+ static int *kbinput = NULL;
+ /* The input buffer. */
+ static size_t kbinput_len = 0;
+ /* The length of the input buffer. */
+ const sc *s;
+ const subnfunc *f;
+
+ *have_shortcut = FALSE;
+ *ran_func = FALSE;
+ *finished = FALSE;
+
+ /* Read in a character. */
+ input = get_kbinput(bottomwin, meta_key, func_key);
+
+#ifndef DISABLE_MOUSE
+ if (allow_funcs) {
+ /* If we got a mouse click and it was on a shortcut, read in the
+ * shortcut character. */
+ if (*func_key && input == KEY_MOUSE) {
+ if (do_statusbar_mouse() == 1)
+ input = get_kbinput(bottomwin, meta_key, func_key);
+ else {
+ *meta_key = FALSE;
+ *func_key = FALSE;
+ input = ERR;
+ }
+ }
+ }
+#endif
+
+ /* Check for a shortcut in the current list. */
+ s = get_shortcut(currmenu, &input, meta_key, func_key);
+
+ /* If we got a shortcut from the current list, or a "universal"
+ * statusbar prompt shortcut, set have_shortcut to TRUE. */
+ *have_shortcut = (s != NULL);
+
+ /* If we got a non-high-bit control key, a meta key sequence, or a
+ * function key, and it's not a shortcut or toggle, throw it out. */
+ if (!*have_shortcut) {
+ if (is_ascii_cntrl_char(input) || *meta_key || *func_key) {
+ beep();
+ *meta_key = FALSE;
+ *func_key = FALSE;
+ input = ERR;
+ }
+ }
+
+ if (allow_funcs) {
+ /* If we got a character, and it isn't a shortcut or toggle,
+ * it's a normal text character. Display the warning if we're
+ * in view mode, or add the character to the input buffer if
+ * we're not. */
+ if (input != ERR && !*have_shortcut) {
+ /* If we're using restricted mode, the filename isn't blank,
+ * and we're at the "Write File" prompt, disable text
+ * input. */
+ if (!ISSET(RESTRICTED) || openfile->filename[0] == '\0' ||
+ currmenu != MWRITEFILE) {
+ kbinput_len++;
+ kbinput = (int *)nrealloc(kbinput, kbinput_len *
+ sizeof(int));
+ kbinput[kbinput_len - 1] = input;
+ }
+ }
+
+ /* If we got a shortcut, or if there aren't any other characters
+ * waiting after the one we read in, we need to display all the
+ * characters in the input buffer if it isn't empty. */
+ if (*have_shortcut || get_key_buffer_len() == 0) {
+ if (kbinput != NULL) {
+ /* Display all the characters in the input buffer at
+ * once, filtering out control characters. */
+ char *output = charalloc(kbinput_len + 1);
+ size_t i;
+ bool got_enter;
+ /* Whether we got the Enter key. */
+
+ for (i = 0; i < kbinput_len; i++)
+ output[i] = (char)kbinput[i];
+ output[i] = '\0';
+
+ do_statusbar_output(output, kbinput_len, &got_enter,
+ FALSE);
+
+ free(output);
+
+ /* Empty the input buffer. */
+ kbinput_len = 0;
+ free(kbinput);
+ kbinput = NULL;
+ }
+ }
+
+ if (*have_shortcut) {
+ if (s->scfunc == do_tab || s->scfunc == do_enter_void)
+ ;
+ else if (s->scfunc == total_refresh)
+ total_statusbar_refresh(refresh_func);
+ else if (s->scfunc == do_cut_text_void) {
+ /* If we're using restricted mode, the filename
+ * isn't blank, and we're at the "Write File"
+ * prompt, disable Cut. */
+ if (!ISSET(RESTRICTED) || openfile->filename[0] ==
+ '\0' || currmenu != MWRITEFILE)
+ do_statusbar_cut_text();
+ } else if (s->scfunc == do_right)
+ do_statusbar_right();
+ else if (s->scfunc == do_left)
+ do_statusbar_left();
+
+#ifndef NANO_TINY
+ else if (s->scfunc == do_next_word_void)
+ do_statusbar_next_word(FALSE);
+ else if (s->scfunc == do_prev_word_void)
+ do_statusbar_prev_word(FALSE);
+#endif
+ else if (s->scfunc == do_home)
+ do_statusbar_home();
+ else if (s->scfunc == do_end)
+ do_statusbar_end();
+
+#ifndef NANO_TINY
+ else if (s->scfunc == do_find_bracket)
+ do_statusbar_find_bracket();
+#endif
+ else if (s->scfunc == do_verbatim_input) {
+ /* If we're using restricted mode, the filename
+ * isn't blank, and we're at the "Write File"
+ * prompt, disable verbatim input. */
+ if (!ISSET(RESTRICTED) ||
+ openfile->filename[0] == '\0' ||
+ currmenu != MWRITEFILE) {
+ bool got_enter;
+ /* Whether we got the Enter key. */
+
+ do_statusbar_verbatim_input(&got_enter);
+
+ /* If we got the Enter key, remove it from
+ * the input buffer, set input to the key
+ * value for Enter, and set finished to TRUE
+ * to indicate that we're done. */
+ if (got_enter) {
+ get_input(NULL, 1);
+ input = sc_seq_or(do_enter_void, 0);
+ *finished = TRUE;
+ }
+ }
+ } else if (s->scfunc == do_delete) {
+ /* If we're using restricted mode, the filename
+ * isn't blank, and we're at the "Write File"
+ * prompt, disable Delete. */
+ if (!ISSET(RESTRICTED) || openfile->filename[0] ==
+ '\0' || currmenu != MWRITEFILE)
+ do_statusbar_delete();
+ } else if (s->scfunc == do_backspace) {
+ /* If we're using restricted mode, the filename
+ * isn't blank, and we're at the "Write File"
+ * prompt, disable Backspace. */
+ if (!ISSET(RESTRICTED) || openfile->filename[0] ==
+ '\0' || currmenu != MWRITEFILE)
+ do_statusbar_backspace();
+ } else {
+ /* Handle the normal statusbar prompt shortcuts, setting
+ * ran_func to TRUE if we try to run their associated
+ * functions and setting finished to TRUE to indicate
+ * that we're done after running or trying to run their
+ * associated functions. */
+
+ f = sctofunc((sc *) s);
+ if (s->scfunc != 0 && s->execute == TRUE) {
+ *ran_func = TRUE;
+ if (f && (!ISSET(VIEW_MODE) || (f->viewok)))
+ f->scfunc();
+ }
+ *finished = TRUE;
+ }
+ }
+ }
+
+ return input;
+}
+
+#ifndef DISABLE_MOUSE
+/* Handle a mouse click on the statusbar prompt or the shortcut list. */
+int do_statusbar_mouse(void)
+{
+ int mouse_x, mouse_y;
+ int retval = get_mouseinput(&mouse_x, &mouse_y, TRUE);
+
+ /* We can click on the statusbar window text to move the cursor. */
+ if (retval == 0 && wmouse_trafo(bottomwin, &mouse_y, &mouse_x,
+ FALSE)) {
+ size_t start_col;
+
+ assert(prompt != NULL);
+
+ start_col = strlenpt(prompt) + 2;
+
+ /* Move to where the click occurred. */
+ if (mouse_x >= start_col && mouse_y == 0) {
+ size_t pww_save = statusbar_pww;
+
+ statusbar_x = actual_x(answer,
+ get_statusbar_page_start(start_col, start_col +
+ statusbar_xplustabs()) + mouse_x - start_col);
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+ }
+ }
+
+ return retval;
+}
+#endif
+
+/* The user typed output_len multibyte characters. Add them to the
+ * statusbar prompt, setting got_enter to TRUE if we get a newline, and
+ * filtering out all ASCII control characters if allow_cntrls is
+ * TRUE. */
+void do_statusbar_output(char *output, size_t output_len, bool
+ *got_enter, bool allow_cntrls)
+{
+ size_t answer_len, i = 0;
+ char *char_buf = charalloc(mb_cur_max());
+ int char_buf_len;
+
+ assert(answer != NULL);
+
+ answer_len = strlen(answer);
+ *got_enter = FALSE;
+
+ while (i < output_len) {
+ /* If allow_cntrls is TRUE, convert nulls and newlines
+ * properly. */
+ if (allow_cntrls) {
+ /* Null to newline, if needed. */
+ if (output[i] == '\0')
+ output[i] = '\n';
+ /* Newline to Enter, if needed. */
+ else if (output[i] == '\n') {
+ /* Set got_enter to TRUE to indicate that we got the
+ * Enter key, put back the rest of the characters in
+ * output so that they can be parsed and output again,
+ * and get out. */
+ *got_enter = TRUE;
+ unparse_kbinput(output + i, output_len - i);
+ return;
+ }
+ }
+
+ /* Interpret the next multibyte character. */
+ char_buf_len = parse_mbchar(output + i, char_buf, NULL);
+
+ i += char_buf_len;
+
+ /* If allow_cntrls is FALSE, filter out an ASCII control
+ * character. */
+ if (!allow_cntrls && is_ascii_cntrl_char(*(output + i -
+ char_buf_len)))
+ continue;
+
+ /* More dangerousness fun =) */
+ answer = charealloc(answer, answer_len + (char_buf_len * 2));
+
+ assert(statusbar_x <= answer_len);
+
+ charmove(answer + statusbar_x + char_buf_len,
+ answer + statusbar_x, answer_len - statusbar_x +
+ char_buf_len);
+ strncpy(answer + statusbar_x, char_buf, char_buf_len);
+ answer_len += char_buf_len;
+
+ statusbar_x += char_buf_len;
+ }
+
+ free(char_buf);
+
+ statusbar_pww = statusbar_xplustabs();
+
+ update_statusbar_line(answer, statusbar_x);
+}
+
+/* Move to the beginning of the prompt text. If the SMART_HOME flag is
+ * set, move to the first non-whitespace character of the prompt text if
+ * we're not already there, or to the beginning of the prompt text if we
+ * are. */
+void do_statusbar_home(void)
+{
+ size_t pww_save = statusbar_pww;
+
+#ifndef NANO_TINY
+ if (ISSET(SMART_HOME)) {
+ size_t statusbar_x_save = statusbar_x;
+
+ statusbar_x = indent_length(answer);
+
+ if (statusbar_x == statusbar_x_save ||
+ statusbar_x == strlen(answer))
+ statusbar_x = 0;
+
+ statusbar_pww = statusbar_xplustabs();
+ } else {
+#endif
+ statusbar_x = 0;
+ statusbar_pww = statusbar_xplustabs();
+#ifndef NANO_TINY
+ }
+#endif
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+}
+
+/* Move to the end of the prompt text. */
+void do_statusbar_end(void)
+{
+ size_t pww_save = statusbar_pww;
+
+ statusbar_x = strlen(answer);
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+}
+
+/* Move left one character. */
+void do_statusbar_left(void)
+{
+ if (statusbar_x > 0) {
+ size_t pww_save = statusbar_pww;
+
+ statusbar_x = move_mbleft(answer, statusbar_x);
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+ }
+}
+
+/* Move right one character. */
+void do_statusbar_right(void)
+{
+ if (statusbar_x < strlen(answer)) {
+ size_t pww_save = statusbar_pww;
+
+ statusbar_x = move_mbright(answer, statusbar_x);
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+ }
+}
+
+/* Backspace over one character. */
+void do_statusbar_backspace(void)
+{
+ if (statusbar_x > 0) {
+ do_statusbar_left();
+ do_statusbar_delete();
+ }
+}
+
+/* Delete one character. */
+void do_statusbar_delete(void)
+{
+ statusbar_pww = statusbar_xplustabs();
+
+ if (answer[statusbar_x] != '\0') {
+ int char_buf_len = parse_mbchar(answer + statusbar_x, NULL,
+ NULL);
+ size_t line_len = strlen(answer + statusbar_x);
+
+ assert(statusbar_x < strlen(answer));
+
+ charmove(answer + statusbar_x, answer + statusbar_x +
+ char_buf_len, strlen(answer) - statusbar_x -
+ char_buf_len + 1);
+
+ null_at(&answer, statusbar_x + line_len - char_buf_len);
+
+ update_statusbar_line(answer, statusbar_x);
+ }
+}
+
+/* Move text from the prompt into oblivion. */
+void do_statusbar_cut_text(void)
+{
+ assert(answer != NULL);
+
+#ifndef NANO_TINY
+ if (ISSET(CUT_TO_END))
+ null_at(&answer, statusbar_x);
+ else {
+#endif
+ null_at(&answer, 0);
+ statusbar_x = 0;
+ statusbar_pww = statusbar_xplustabs();
+#ifndef NANO_TINY
+ }
+#endif
+
+ update_statusbar_line(answer, statusbar_x);
+}
+
+#ifndef NANO_TINY
+/* Move to the next word in the prompt text. If allow_punct is TRUE,
+ * treat punctuation as part of a word. Return TRUE if we started on a
+ * word, and FALSE otherwise. */
+bool do_statusbar_next_word(bool allow_punct)
+{
+ size_t pww_save = statusbar_pww;
+ char *char_mb;
+ int char_mb_len;
+ bool end_line = FALSE, started_on_word = FALSE;
+
+ assert(answer != NULL);
+
+ char_mb = charalloc(mb_cur_max());
+
+ /* Move forward until we find the character after the last letter of
+ * the current word. */
+ while (!end_line) {
+ char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
+
+ /* If we've found it, stop moving forward through the current
+ * line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ /* If we haven't found it, then we've started on a word, so set
+ * started_on_word to TRUE. */
+ started_on_word = TRUE;
+
+ if (answer[statusbar_x] == '\0')
+ end_line = TRUE;
+ else
+ statusbar_x += char_mb_len;
+ }
+
+ /* Move forward until we find the first letter of the next word. */
+ if (answer[statusbar_x] == '\0')
+ end_line = TRUE;
+ else
+ statusbar_x += char_mb_len;
+
+ while (!end_line) {
+ char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
+
+ /* If we've found it, stop moving forward through the current
+ * line. */
+ if (is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (answer[statusbar_x] == '\0')
+ end_line = TRUE;
+ else
+ statusbar_x += char_mb_len;
+ }
+
+ free(char_mb);
+
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+
+ /* Return whether we started on a word. */
+ return started_on_word;
+}
+
+/* Move to the previous word in the prompt text. If allow_punct is
+ * TRUE, treat punctuation as part of a word. Return TRUE if we started
+ * on a word, and FALSE otherwise. */
+bool do_statusbar_prev_word(bool allow_punct)
+{
+ size_t pww_save = statusbar_pww;
+ char *char_mb;
+ int char_mb_len;
+ bool begin_line = FALSE, started_on_word = FALSE;
+
+ assert(answer != NULL);
+
+ char_mb = charalloc(mb_cur_max());
+
+ /* Move backward until we find the character before the first letter
+ * of the current word. */
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
+
+ /* If we've found it, stop moving backward through the current
+ * line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ /* If we haven't found it, then we've started on a word, so set
+ * started_on_word to TRUE. */
+ started_on_word = TRUE;
+
+ if (statusbar_x == 0)
+ begin_line = TRUE;
+ else
+ statusbar_x = move_mbleft(answer, statusbar_x);
+ }
+
+ /* Move backward until we find the last letter of the previous
+ * word. */
+ if (statusbar_x == 0)
+ begin_line = TRUE;
+ else
+ statusbar_x = move_mbleft(answer, statusbar_x);
+
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(answer + statusbar_x, char_mb, NULL);
+
+ /* If we've found it, stop moving backward through the current
+ * line. */
+ if (is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (statusbar_x == 0)
+ begin_line = TRUE;
+ else
+ statusbar_x = move_mbleft(answer, statusbar_x);
+ }
+
+ /* If we've found it, move backward until we find the character
+ * before the first letter of the previous word. */
+ if (!begin_line) {
+ if (statusbar_x == 0)
+ begin_line = TRUE;
+ else
+ statusbar_x = move_mbleft(answer, statusbar_x);
+
+ while (!begin_line) {
+ char_mb_len = parse_mbchar(answer + statusbar_x, char_mb,
+ NULL);
+
+ /* If we've found it, stop moving backward through the
+ * current line. */
+ if (!is_word_mbchar(char_mb, allow_punct))
+ break;
+
+ if (statusbar_x == 0)
+ begin_line = TRUE;
+ else
+ statusbar_x = move_mbleft(answer, statusbar_x);
+ }
+
+ /* If we've found it, move forward to the first letter of the
+ * previous word. */
+ if (!begin_line)
+ statusbar_x += char_mb_len;
+ }
+
+ free(char_mb);
+
+ statusbar_pww = statusbar_xplustabs();
+
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+
+ /* Return whether we started on a word. */
+ return started_on_word;
+}
+#endif /* !NANO_TINY */
+
+/* Get verbatim input. Set got_enter to TRUE if we got the Enter key as
+ * part of the verbatim input. */
+void do_statusbar_verbatim_input(bool *got_enter)
+{
+ int *kbinput;
+ size_t kbinput_len, i;
+ char *output;
+
+ *got_enter = FALSE;
+
+ /* Read in all the verbatim characters. */
+ kbinput = get_verbatim_kbinput(bottomwin, &kbinput_len);
+
+ /* Display all the verbatim characters at once, not filtering out
+ * control characters. */
+ output = charalloc(kbinput_len + 1);
+
+ for (i = 0; i < kbinput_len; i++)
+ output[i] = (char)kbinput[i];
+ output[i] = '\0';
+
+ do_statusbar_output(output, kbinput_len, got_enter, TRUE);
+
+ free(output);
+}
+
+#ifndef NANO_TINY
+/* Search for a match to one of the two characters in bracket_set. If
+ * reverse is TRUE, search backwards for the leftmost bracket.
+ * Otherwise, search forwards for the rightmost bracket. Return TRUE if
+ * we found a match, and FALSE otherwise. */
+bool find_statusbar_bracket_match(bool reverse, const char
+ *bracket_set)
+{
+ const char *rev_start = NULL, *found = NULL;
+
+ assert(mbstrlen(bracket_set) == 2);
+
+ /* rev_start might end up 1 character before the start or after the
+ * end of the line. This won't be a problem because we'll skip over
+ * it below in that case. */
+ rev_start = reverse ? answer + (statusbar_x - 1) : answer +
+ (statusbar_x + 1);
+
+ while (TRUE) {
+ /* Look for either of the two characters in bracket_set.
+ * rev_start can be 1 character before the start or after the
+ * end of the line. In either case, just act as though no match
+ * is found. */
+ found = ((rev_start > answer && *(rev_start - 1) == '\0') ||
+ rev_start < answer) ? NULL : (reverse ?
+ mbrevstrpbrk(answer, bracket_set, rev_start) :
+ mbstrpbrk(rev_start, bracket_set));
+
+ /* We've found a potential match. */
+ if (found != NULL)
+ break;
+
+ /* We've reached the start or end of the statusbar text, so
+ * get out. */
+ return FALSE;
+ }
+
+ /* We've definitely found something. */
+ statusbar_x = found - answer;
+ statusbar_pww = statusbar_xplustabs();
+
+ return TRUE;
+}
+
+/* Search for a match to the bracket at the current cursor position, if
+ * there is one. */
+void do_statusbar_find_bracket(void)
+{
+ size_t statusbar_x_save, pww_save;
+ const char *ch;
+ /* The location in matchbrackets of the bracket at the current
+ * cursor position. */
+ int ch_len;
+ /* The length of ch in bytes. */
+ const char *wanted_ch;
+ /* The location in matchbrackets of the bracket complementing
+ * the bracket at the current cursor position. */
+ int wanted_ch_len;
+ /* The length of wanted_ch in bytes. */
+ char *bracket_set;
+ /* The pair of characters in ch and wanted_ch. */
+ size_t i;
+ /* Generic loop variable. */
+ size_t matchhalf;
+ /* The number of single-byte characters in one half of
+ * matchbrackets. */
+ size_t mbmatchhalf;
+ /* The number of multibyte characters in one half of
+ * matchbrackets. */
+ size_t count = 1;
+ /* The initial bracket count. */
+ bool reverse;
+ /* The direction we search. */
+ char *found_ch;
+ /* The character we find. */
+
+ assert(mbstrlen(matchbrackets) % 2 == 0);
+
+ ch = answer + statusbar_x;
+
+ if (ch == '\0' || (ch = mbstrchr(matchbrackets, ch)) == NULL)
+ return;
+
+ /* Save where we are. */
+ statusbar_x_save = statusbar_x;
+ pww_save = statusbar_pww;
+
+ /* If we're on an opening bracket, which must be in the first half
+ * of matchbrackets, we want to search forwards for a closing
+ * bracket. If we're on a closing bracket, which must be in the
+ * second half of matchbrackets, we want to search backwards for an
+ * opening bracket. */
+ matchhalf = 0;
+ mbmatchhalf = mbstrlen(matchbrackets) / 2;
+
+ for (i = 0; i < mbmatchhalf; i++)
+ matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL,
+ NULL);
+
+ reverse = ((ch - matchbrackets) >= matchhalf);
+
+ /* If we're on an opening bracket, set wanted_ch to the character
+ * that's matchhalf characters after ch. If we're on a closing
+ * bracket, set wanted_ch to the character that's matchhalf
+ * characters before ch. */
+ wanted_ch = ch;
+
+ while (mbmatchhalf > 0) {
+ if (reverse)
+ wanted_ch = matchbrackets + move_mbleft(matchbrackets,
+ wanted_ch - matchbrackets);
+ else
+ wanted_ch += move_mbright(wanted_ch, 0);
+
+ mbmatchhalf--;
+ }
+
+ ch_len = parse_mbchar(ch, NULL, NULL);
+ wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL);
+
+ /* Fill bracket_set in with the values of ch and wanted_ch. */
+ bracket_set = charalloc((mb_cur_max() * 2) + 1);
+ strncpy(bracket_set, ch, ch_len);
+ strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len);
+ null_at(&bracket_set, ch_len + wanted_ch_len);
+
+ found_ch = charalloc(mb_cur_max() + 1);
+
+ while (TRUE) {
+ if (find_statusbar_bracket_match(reverse, bracket_set)) {
+ /* If we found an identical bracket, increment count. If we
+ * found a complementary bracket, decrement it. */
+ parse_mbchar(answer + statusbar_x, found_ch, NULL);
+ count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1;
+
+ /* If count is zero, we've found a matching bracket. Update
+ * the statusbar prompt and get out. */
+ if (count == 0) {
+ if (need_statusbar_horizontal_update(pww_save))
+ update_statusbar_line(answer, statusbar_x);
+ break;
+ }
+ } else {
+ /* We didn't find either an opening or closing bracket.
+ * Restore where we were, and get out. */
+ statusbar_x = statusbar_x_save;
+ statusbar_pww = pww_save;
+ break;
+ }
+ }
+
+ /* Clean up. */
+ free(bracket_set);
+ free(found_ch);
+}
+#endif /* !NANO_TINY */
+
+/* Return the placewewant associated with statusbar_x, i.e. the
+ * zero-based column position of the cursor. The value will be no
+ * smaller than statusbar_x. */
+size_t statusbar_xplustabs(void)
+{
+ return strnlenpt(answer, statusbar_x);
+}
+
+/* nano scrolls horizontally within a line in chunks. This function
+ * returns the column number of the first character displayed in the
+ * statusbar prompt when the cursor is at the given column with the
+ * prompt ending at start_col. Note that (0 <= column -
+ * get_statusbar_page_start(column) < COLS). */
+size_t get_statusbar_page_start(size_t start_col, size_t column)
+{
+ if (column == start_col || column < COLS - 1)
+ return 0;
+ else
+ return column - start_col - (column - start_col) % (COLS -
+ start_col - 1);
+}
+
+/* Put the cursor in the statusbar prompt at statusbar_x. */
+void reset_statusbar_cursor(void)
+{
+ size_t start_col = strlenpt(prompt) + 2;
+ size_t xpt = statusbar_xplustabs();
+
+ wmove(bottomwin, 0, start_col + xpt -
+ get_statusbar_page_start(start_col, start_col + xpt));
+}
+
+/* Repaint the statusbar when getting a character in
+ * get_prompt_string(). The statusbar text line will be displayed
+ * starting with curranswer[index]. */
+void update_statusbar_line(const char *curranswer, size_t index)
+{
+ size_t start_col, page_start;
+ char *expanded;
+
+ assert(prompt != NULL && index <= strlen(curranswer));
+
+ start_col = strlenpt(prompt) + 2;
+ index = strnlenpt(curranswer, index);
+ page_start = get_statusbar_page_start(start_col, start_col + index);
+
+ wattron(bottomwin, reverse_attr);
+
+ blank_statusbar();
+
+ mvwaddnstr(bottomwin, 0, 0, prompt, actual_x(prompt, COLS - 2));
+ waddch(bottomwin, ':');
+ waddch(bottomwin, (page_start == 0) ? ' ' : '$');
+
+ expanded = display_string(curranswer, page_start, COLS - start_col -
+ 1, FALSE);
+ waddstr(bottomwin, expanded);
+ free(expanded);
+
+ wattroff(bottomwin, reverse_attr);
+ statusbar_pww = statusbar_xplustabs();
+ reset_statusbar_cursor();
+ wnoutrefresh(bottomwin);
+}
+
+/* Return TRUE if we need an update after moving horizontally, and FALSE
+ * otherwise. We need one if pww_save and statusbar_pww are on
+ * different pages. */
+bool need_statusbar_horizontal_update(size_t pww_save)
+{
+ size_t start_col = strlenpt(prompt) + 2;
+
+ return get_statusbar_page_start(start_col, start_col + pww_save) !=
+ get_statusbar_page_start(start_col, start_col + statusbar_pww);
+}
+
+/* Unconditionally redraw the entire screen, and then refresh it using
+ * refresh_func(). */
+void total_statusbar_refresh(void (*refresh_func)(void))
+{
+ total_redraw();
+ refresh_func();
+}
+
+/* Get a string of input at the statusbar prompt. This should only be
+ * called from do_prompt(). */
+const sc *get_prompt_string(int *actual, bool allow_tabs,
+#ifndef DISABLE_TABCOMP
+ bool allow_files,
+#endif
+ const char *curranswer,
+ bool *meta_key, bool *func_key,
+#ifndef NANO_TINY
+ filestruct **history_list,
+#endif
+ void (*refresh_func)(void), int menu
+#ifndef DISABLE_TABCOMP
+ , bool *list
+#endif
+ )
+{
+ int kbinput = ERR;
+ bool have_shortcut, ran_func, finished;
+ size_t curranswer_len;
+ const sc *s;
+#ifndef DISABLE_TABCOMP
+ bool tabbed = FALSE;
+ /* Whether we've pressed Tab. */
+#endif
+#ifndef NANO_TINY
+ char *history = NULL;
+ /* The current history string. */
+ char *magichistory = NULL;
+ /* The temporary string typed at the bottom of the history, if
+ * any. */
+#ifndef DISABLE_TABCOMP
+ int last_kbinput = ERR;
+ /* The key we pressed before the current key. */
+ size_t complete_len = 0;
+ /* The length of the original string that we're trying to
+ * tab complete, if any. */
+#endif
+#endif /* !NANO_TINY */
+
+ answer = mallocstrcpy(answer, curranswer);
+ curranswer_len = strlen(answer);
+
+ /* If reset_statusbar_x is TRUE, restore statusbar_x and
+ * statusbar_pww to what they were before this prompt. Then, if
+ * statusbar_x is uninitialized or past the end of curranswer, put
+ * statusbar_x at the end of the string and update statusbar_pww
+ * based on it. We do these things so that the cursor position
+ * stays at the right place if a prompt-changing toggle is pressed,
+ * or if this prompt was started from another prompt and we cancel
+ * out of it. */
+ if (reset_statusbar_x) {
+ statusbar_x = old_statusbar_x;
+ statusbar_pww = old_pww;
+ }
+
+ if (statusbar_x == (size_t)-1 || statusbar_x > curranswer_len) {
+ statusbar_x = curranswer_len;
+ statusbar_pww = statusbar_xplustabs();
+ }
+
+ currmenu = menu;
+
+#ifdef DEBUG
+fprintf(stderr, "get_prompt_string: answer = \"%s\", statusbar_x = %lu\n", answer, (unsigned long) statusbar_x);
+#endif
+
+ update_statusbar_line(answer, statusbar_x);
+
+ /* Refresh the edit window and the statusbar before getting
+ * input. */
+ wnoutrefresh(edit);
+ wnoutrefresh(bottomwin);
+
+ /* If we're using restricted mode, we aren't allowed to change the
+ * name of the current file once it has one, because that would
+ * allow writing to files not specified on the command line. In
+ * this case, disable all keys that would change the text if the
+ * filename isn't blank and we're at the "Write File" prompt. */
+ while (1) {
+ kbinput = do_statusbar_input(meta_key, func_key, &have_shortcut,
+ &ran_func, &finished, TRUE, refresh_func);
+ assert(statusbar_x <= strlen(answer));
+
+ s = get_shortcut(currmenu, &kbinput, meta_key, func_key);
+
+ if (s)
+ if (s->scfunc == do_cancel || s->scfunc == do_enter_void)
+ break;
+
+#ifndef DISABLE_TABCOMP
+ if (s && s->scfunc != do_tab)
+ tabbed = FALSE;
+#endif
+
+#ifndef DISABLE_TABCOMP
+#ifndef NANO_TINY
+ if (s && s->scfunc == do_tab) {
+ if (history_list != NULL) {
+ if (last_kbinput != sc_seq_or(do_tab, NANO_CONTROL_I))
+ complete_len = strlen(answer);
+
+ if (complete_len > 0) {
+ answer = mallocstrcpy(answer,
+ get_history_completion(history_list,
+ answer, complete_len));
+ statusbar_x = strlen(answer);
+ }
+ } else
+#endif /* !NANO_TINY */
+ if (allow_tabs)
+ answer = input_tab(answer, allow_files,
+ &statusbar_x, &tabbed, refresh_func, list);
+
+ update_statusbar_line(answer, statusbar_x);
+ } else
+#endif /* !DISABLE_TABCOMP */
+#ifndef NANO_TINY
+ if (s && s->scfunc == get_history_older_void) {
+ if (history_list != NULL) {
+ /* If we're scrolling up at the bottom of the
+ * history list and answer isn't blank, save answer
+ * in magichistory. */
+ if ((*history_list)->next == NULL &&
+ answer[0] != '\0')
+ magichistory = mallocstrcpy(magichistory,
+ answer);
+
+ /* Get the older search from the history list and
+ * save it in answer. If there is no older search,
+ * don't do anything. */
+ if ((history =
+ get_history_older(history_list)) != NULL) {
+ answer = mallocstrcpy(answer, history);
+ statusbar_x = strlen(answer);
+ }
+
+ update_statusbar_line(answer, statusbar_x);
+
+ /* This key has a shortcut list entry when it's used
+ * to move to an older search, which means that
+ * finished has been set to TRUE. Set it back to
+ * FALSE here, so that we aren't kicked out of the
+ * statusbar prompt. */
+ finished = FALSE;
+ }
+ } else if (s && s->scfunc == get_history_newer_void) {
+ if (history_list != NULL) {
+ /* Get the newer search from the history list and
+ * save it in answer. If there is no newer search,
+ * don't do anything. */
+ if ((history =
+ get_history_newer(history_list)) != NULL) {
+ answer = mallocstrcpy(answer, history);
+ statusbar_x = strlen(answer);
+ }
+
+ /* If, after scrolling down, we're at the bottom of
+ * the history list, answer is blank, and
+ * magichistory is set, save magichistory in
+ * answer. */
+ if ((*history_list)->next == NULL &&
+ *answer == '\0' && magichistory != NULL) {
+ answer = mallocstrcpy(answer, magichistory);
+ statusbar_x = strlen(answer);
+ }
+
+ update_statusbar_line(answer, statusbar_x);
+
+ /* This key has a shortcut list entry when it's used
+ * to move to a newer search, which means that
+ * finished has been set to TRUE. Set it back to
+ * FALSE here, so that we aren't kicked out of the
+ * statusbar prompt. */
+ finished = FALSE;
+ }
+ } else
+#endif /* !NANO_TINY */
+ if (s && s->scfunc == do_help_void) {
+ update_statusbar_line(answer, statusbar_x);
+
+ /* This key has a shortcut list entry when it's used to
+ * go to the help browser or display a message
+ * indicating that help is disabled, which means that
+ * finished has been set to TRUE. Set it back to FALSE
+ * here, so that we aren't kicked out of the statusbar
+ * prompt. */
+ finished = FALSE;
+ }
+
+ /* If we have a shortcut with an associated function, break out
+ * if we're finished after running or trying to run the
+ * function. */
+ if (finished)
+ break;
+
+#if !defined(NANO_TINY) && !defined(DISABLE_TABCOMP)
+ last_kbinput = kbinput;
+#endif
+
+ reset_statusbar_cursor();
+ wnoutrefresh(bottomwin);
+ }
+
+
+#ifndef NANO_TINY
+ /* Set the current position in the history list to the bottom and
+ * free magichistory, if we need to. */
+ if (history_list != NULL) {
+ history_reset(*history_list);
+
+ if (magichistory != NULL)
+ free(magichistory);
+ }
+#endif
+
+
+ /* We've finished putting in an answer or run a normal shortcut's
+ * associated function, so reset statusbar_x and statusbar_pww. If
+ * we've finished putting in an answer, reset the statusbar cursor
+ * position too. */
+ if (s) {
+ if (s->scfunc == do_cancel || s->scfunc == do_enter_void ||
+ ran_func) {
+ statusbar_x = old_statusbar_x;
+ statusbar_pww = old_pww;
+
+ if (!ran_func)
+ reset_statusbar_x = TRUE;
+ /* Otherwise, we're still putting in an answer or a shortcut with
+ * an associated function, so leave the statusbar cursor position
+ * alone. */
+ } else
+ reset_statusbar_x = FALSE;
+ }
+
+ *actual = kbinput;
+ return s;
+}
+
+/* Ask a question on the statusbar. The prompt will be stored in the
+ * static prompt, which should be NULL initially, and the answer will be
+ * stored in the answer global. Returns -1 on aborted enter, -2 on a
+ * blank string, and 0 otherwise, the valid shortcut key caught.
+ * curranswer is any editable text that we want to put up by default,
+ * and refresh_func is the function we want to call to refresh the edit
+ * window.
+ *
+ * The allow_tabs parameter indicates whether we should allow tabs to be
+ * interpreted. The allow_files parameter indicates whether we should
+ * allow all files (as opposed to just directories) to be tab
+ * completed. */
+int do_prompt(bool allow_tabs,
+#ifndef DISABLE_TABCOMP
+ bool allow_files,
+#endif
+ int menu, const char *curranswer,
+ bool *meta_key, bool *func_key,
+#ifndef NANO_TINY
+ filestruct **history_list,
+#endif
+ void (*refresh_func)(void), const char *msg, ...)
+{
+ va_list ap;
+ int retval;
+ const sc *s;
+#ifndef DISABLE_TABCOMP
+ bool list = FALSE;
+#endif
+
+ /* prompt has been freed and set to NULL unless the user resized
+ * while at the statusbar prompt. */
+ if (prompt != NULL)
+ free(prompt);
+
+ prompt = charalloc(((COLS - 4) * mb_cur_max()) + 1);
+
+ bottombars(menu);
+
+ va_start(ap, msg);
+ vsnprintf(prompt, (COLS - 4) * mb_cur_max(), msg, ap);
+ va_end(ap);
+ null_at(&prompt, actual_x(prompt, COLS - 4));
+
+ s = get_prompt_string(&retval, allow_tabs,
+#ifndef DISABLE_TABCOMP
+ allow_files,
+#endif
+ curranswer,
+ meta_key, func_key,
+#ifndef NANO_TINY
+ history_list,
+#endif
+ refresh_func, menu
+#ifndef DISABLE_TABCOMP
+ , &list
+#endif
+ );
+
+ free(prompt);
+ prompt = NULL;
+
+ /* We're done with the prompt, so save the statusbar cursor
+ * position. */
+ old_statusbar_x = statusbar_x;
+ old_pww = statusbar_pww;
+
+ /* If we left the prompt via Cancel or Enter, set the return value
+ * properly. */
+ if (s && s->scfunc == do_cancel)
+ retval = -1;
+ else if (s && s->scfunc == do_enter_void)
+ retval = (*answer == '\0') ? -2 : 0;
+
+ blank_statusbar();
+ wnoutrefresh(bottomwin);
+
+#ifdef DEBUG
+ fprintf(stderr, "answer = \"%s\"\n", answer);
+#endif
+
+#ifndef DISABLE_TABCOMP
+ /* If we've done tab completion, there might be a list of filename
+ * matches on the edit window at this point. Make sure that they're
+ * cleared off. */
+ if (list)
+ refresh_func();
+#endif
+
+ return retval;
+}
+
+/* This function forces a reset of the statusbar cursor position. It
+ * should be called when we get out of all statusbar prompts. */
+void do_prompt_abort(void)
+{
+ /* Uninitialize the old cursor position in answer. */
+ old_statusbar_x = (size_t)-1;
+ old_pww = (size_t)-1;
+
+ reset_statusbar_x = TRUE;
+}
+
+/* Ask a simple Yes/No (and optionally All) question, specified in msg,
+ * on the statusbar. Return 1 for Yes, 0 for No, 2 for All (if all is
+ * TRUE when passed in), and -1 for Cancel. */
+int do_yesno_prompt(bool all, const char *msg)
+{
+ int ok = -2, width = 16;
+ const char *yesstr; /* String of Yes characters accepted. */
+ const char *nostr; /* Same for No. */
+ const char *allstr; /* And All, surprise! */
+ const sc *s;
+ int oldmenu = currmenu;
+
+ assert(msg != NULL);
+
+ /* yesstr, nostr, and allstr are strings of any length. Each string
+ * consists of all single-byte characters accepted as valid
+ * characters for that value. The first value will be the one
+ * displayed in the shortcuts. */
+ /* TRANSLATORS: For the next three strings, if possible, specify
+ * the single-byte shortcuts for both your language and English.
+ * For example, in French: "OoYy" for "Oui". */
+ yesstr = _("Yy");
+ nostr = _("Nn");
+ allstr = _("Aa");
+
+ if (!ISSET(NO_HELP)) {
+ char shortstr[3];
+ /* Temp string for Yes, No, All. */
+
+ if (COLS < 32)
+ width = COLS / 2;
+
+ /* Clear the shortcut list from the bottom of the screen. */
+ blank_bottombars();
+
+ sprintf(shortstr, " %c", yesstr[0]);
+ wmove(bottomwin, 1, 0);
+ onekey(shortstr, _("Yes"), width);
+
+ if (all) {
+ wmove(bottomwin, 1, width);
+ shortstr[1] = allstr[0];
+ onekey(shortstr, _("All"), width);
+ }
+
+ wmove(bottomwin, 2, 0);
+ shortstr[1] = nostr[0];
+ onekey(shortstr, _("No"), width);
+
+ wmove(bottomwin, 2, 16);
+ onekey("^C", _("Cancel"), width);
+ }
+
+ wattron(bottomwin, reverse_attr);
+
+ blank_statusbar();
+ mvwaddnstr(bottomwin, 0, 0, msg, actual_x(msg, COLS - 1));
+
+ wattroff(bottomwin, reverse_attr);
+
+ /* Refresh the edit window and the statusbar before getting
+ * input. */
+ wnoutrefresh(edit);
+ wnoutrefresh(bottomwin);
+
+ do {
+ int kbinput;
+ bool meta_key, func_key;
+#ifndef DISABLE_MOUSE
+ int mouse_x, mouse_y;
+#endif
+
+ currmenu = MYESNO;
+ kbinput = get_kbinput(bottomwin, &meta_key, &func_key);
+ s = get_shortcut(currmenu, &kbinput, &meta_key, &func_key);
+
+ if (s && s->scfunc == do_cancel)
+ ok = -1;
+#ifndef DISABLE_MOUSE
+ else if (kbinput == KEY_MOUSE) {
+ /* We can click on the Yes/No/All shortcut list to
+ * select an answer. */
+ if (get_mouseinput(&mouse_x, &mouse_y, FALSE) == 0 &&
+ wmouse_trafo(bottomwin, &mouse_y, &mouse_x,
+ FALSE) && mouse_x < (width * 2) &&
+ mouse_y > 0) {
+ int x = mouse_x / width;
+ /* Calculate the x-coordinate relative to the
+ * two columns of the Yes/No/All shortcuts in
+ * bottomwin. */
+ int y = mouse_y - 1;
+ /* Calculate the y-coordinate relative to the
+ * beginning of the Yes/No/All shortcuts in
+ * bottomwin, i.e. with the sizes of topwin,
+ * edit, and the first line of bottomwin
+ * subtracted out. */
+
+ assert(0 <= x && x <= 1 && 0 <= y && y <= 1);
+
+ /* x == 0 means they clicked Yes or No. y == 0
+ * means Yes or All. */
+ ok = -2 * x * y + x - y + 1;
+
+ if (ok == 2 && !all)
+ ok = -2;
+ }
+ }
+#endif /* !DISABLE_MOUSE */
+ else if (s && s->scfunc == total_refresh) {
+ total_redraw();
+ continue;
+ } else {
+ /* Look for the kbinput in the Yes, No and (optionally)
+ * All strings. */
+ if (strchr(yesstr, kbinput) != NULL)
+ ok = 1;
+ else if (strchr(nostr, kbinput) != NULL)
+ ok = 0;
+ else if (all && strchr(allstr, kbinput) != NULL)
+ ok = 2;
+ }
+ } while (ok == -2);
+
+ currmenu = oldmenu;
+ return ok;
+}
diff --git a/src/proto.h b/src/proto.h
new file mode 100644
index 0000000..38ebf6a
--- /dev/null
+++ b/src/proto.h
@@ -0,0 +1,863 @@
+/* $Id: proto.h 4530 2011-02-18 07:30:57Z astyanax $ */
+/**************************************************************************
+ * proto.h *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#ifndef PROTO_H
+#define PROTO_H 1
+
+#include "nano.h"
+
+/* All external variables. See global.c for their descriptions. */
+#ifndef NANO_TINY
+extern sigjmp_buf jump_buf;
+extern bool jump_buf_main;
+extern bool use_undo;
+#endif
+
+#ifndef DISABLE_WRAPJUSTIFY
+extern ssize_t fill;
+extern ssize_t wrap_at;
+#endif
+
+extern char *last_search;
+extern char *last_replace;
+
+extern unsigned flags[4];
+extern WINDOW *topwin;
+extern WINDOW *edit;
+extern WINDOW *bottomwin;
+extern int editwinrows;
+extern int maxrows;
+
+extern filestruct *cutbuffer;
+extern filestruct *cutbottom;
+#ifndef DISABLE_JUSTIFY
+extern filestruct *jusbuffer;
+#endif
+extern partition *filepart;
+extern openfilestruct *openfile;
+
+#ifndef NANO_TINY
+extern char *matchbrackets;
+#endif
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+extern char *whitespace;
+extern int whitespace_len[2];
+extern undo_type last_action;
+#endif
+
+#ifndef DISABLE_JUSTIFY
+extern char *punct;
+extern char *brackets;
+extern char *quotestr;
+#ifdef HAVE_REGEX_H
+extern regex_t quotereg;
+extern int quoterc;
+extern char *quoteerr;
+#else
+extern size_t quotelen;
+#endif
+#endif
+extern bool nodelay_mode;
+extern char *answer;
+
+extern ssize_t tabsize;
+
+#ifndef NANO_TINY
+extern char *backup_dir;
+#endif
+#ifndef DISABLE_OPERATINGDIR
+extern char *operating_dir;
+extern char *full_operating_dir;
+#endif
+
+#ifndef DISABLE_SPELLER
+extern char *alt_speller;
+#endif
+
+extern sc *sclist;
+extern subnfunc *allfuncs;
+#ifdef ENABLE_COLOR
+extern syntaxtype *syntaxes;
+extern char *syntaxstr;
+#endif
+
+extern bool edit_refresh_needed;
+extern const shortcut *currshortcut;
+extern int currmenu;
+
+#ifndef NANO_TINY
+extern filestruct *search_history;
+extern filestruct *searchage;
+extern filestruct *searchbot;
+extern filestruct *replace_history;
+extern filestruct *replaceage;
+extern filestruct *replacebot;
+extern poshiststruct *poshistory;
+void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos);
+#endif
+
+#ifdef HAVE_REGEX_H
+extern regex_t search_regexp;
+extern regmatch_t regmatches[10];
+#endif
+
+extern int reverse_attr;
+
+extern char *homedir;
+
+/* All functions in browser.c. */
+#ifndef DISABLE_BROWSER
+char *do_browser(char *path, DIR *dir);
+char *do_browse_from(const char *inpath);
+void browser_init(const char *path, DIR *dir);
+void parse_browser_input(int *kbinput, bool *meta_key, bool *func_key);
+void browser_refresh(void);
+bool browser_select_filename(const char *needle);
+int filesearch_init(void);
+bool findnextfile(bool no_sameline, size_t begin, const char *needle);
+void findnextfile_wrap_reset(void);
+void filesearch_abort(void);
+void do_filesearch(void);
+void do_fileresearch(void);
+void do_first_file(void);
+void do_last_file(void);
+char *striponedir(const char *path);
+#endif
+
+/* All functions in chars.c. */
+#ifdef ENABLE_UTF8
+void utf8_init(void);
+bool using_utf8(void);
+#endif
+#ifndef HAVE_ISBLANK
+bool nisblank(int c);
+#endif
+#if !defined(HAVE_ISWBLANK) && defined(ENABLE_UTF8)
+bool niswblank(wchar_t wc);
+#endif
+bool is_byte(int c);
+bool is_alnum_mbchar(const char *c);
+bool is_blank_mbchar(const char *c);
+bool is_ascii_cntrl_char(int c);
+bool is_cntrl_char(int c);
+#ifdef ENABLE_UTF8
+bool is_cntrl_wchar(wchar_t wc);
+#endif
+bool is_cntrl_mbchar(const char *c);
+bool is_punct_mbchar(const char *c);
+bool is_word_mbchar(const char *c, bool allow_punct);
+char control_rep(char c);
+#ifdef ENABLE_UTF8
+wchar_t control_wrep(wchar_t c);
+#endif
+char *control_mbrep(const char *c, char *crep, int *crep_len);
+char *mbrep(const char *c, char *crep, int *crep_len);
+int mbwidth(const char *c);
+int mb_cur_max(void);
+char *make_mbchar(long chr, int *chr_mb_len);
+int parse_mbchar(const char *buf, char *chr, size_t *col);
+size_t move_mbleft(const char *buf, size_t pos);
+size_t move_mbright(const char *buf, size_t pos);
+#ifndef HAVE_STRCASECMP
+int nstrcasecmp(const char *s1, const char *s2);
+#endif
+int mbstrcasecmp(const char *s1, const char *s2);
+#ifndef HAVE_STRNCASECMP
+int nstrncasecmp(const char *s1, const char *s2, size_t n);
+#endif
+int mbstrncasecmp(const char *s1, const char *s2, size_t n);
+#ifndef HAVE_STRCASESTR
+char *nstrcasestr(const char *haystack, const char *needle);
+#endif
+char *mbstrcasestr(const char *haystack, const char *needle);
+#if !defined(NANO_TINY) || !defined(DISABLE_TABCOMP)
+char *revstrstr(const char *haystack, const char *needle, const char
+ *rev_start);
+#endif
+#ifndef NANO_TINY
+char *revstrcasestr(const char *haystack, const char *needle, const char
+ *rev_start);
+char *mbrevstrcasestr(const char *haystack, const char *needle, const
+ char *rev_start);
+#endif
+size_t mbstrlen(const char *s);
+#ifndef HAVE_STRNLEN
+size_t nstrnlen(const char *s, size_t maxlen);
+#endif
+size_t mbstrnlen(const char *s, size_t maxlen);
+#if !defined(NANO_TINY) || !defined(DISABLE_JUSTIFY)
+char *mbstrchr(const char *s, const char *c);
+#endif
+#ifndef NANO_TINY
+char *mbstrpbrk(const char *s, const char *accept);
+char *revstrpbrk(const char *s, const char *accept, const char
+ *rev_start);
+char *mbrevstrpbrk(const char *s, const char *accept, const char
+ *rev_start);
+#endif
+#if defined(ENABLE_NANORC) && (!defined(NANO_TINY) || !defined(DISABLE_JUSTIFY))
+bool has_blank_chars(const char *s);
+bool has_blank_mbchars(const char *s);
+#endif
+#ifdef ENABLE_UTF8
+bool is_valid_unicode(wchar_t wc);
+#endif
+#ifdef ENABLE_NANORC
+bool is_valid_mbstring(const char *s);
+#endif
+
+/* All functions in color.c. */
+#ifdef ENABLE_COLOR
+void set_colorpairs(void);
+void color_init(void);
+void color_update(void);
+#endif
+
+/* All functions in cut.c. */
+void cutbuffer_reset(void);
+void cut_line(void);
+#ifndef NANO_TINY
+void cut_marked(void);
+void cut_to_eol(void);
+void cut_to_eof(void);
+#endif
+void do_cut_text(
+#ifndef NANO_TINY
+ bool copy_text, bool cut_till_end, bool undoing
+#else
+ void
+#endif
+ );
+void do_cut_text_void(void);
+#ifndef NANO_TINY
+void do_copy_text(void);
+void do_cut_till_end(void);
+#endif
+void do_uncut_text(void);
+
+/* All functions in files.c. */
+void make_new_buffer(void);
+void initialize_buffer(void);
+void initialize_buffer_text(void);
+void open_buffer(const char *filename, bool undoable);
+#ifndef DISABLE_SPELLER
+void replace_buffer(const char *filename);
+#endif
+void display_buffer(void);
+#ifdef ENABLE_MULTIBUFFER
+void switch_to_prevnext_buffer(bool next);
+void switch_to_prev_buffer_void(void);
+void switch_to_next_buffer_void(void);
+bool close_buffer(void);
+#endif
+filestruct *read_line(char *buf, filestruct *prevnode, bool
+ *first_line_ins, size_t buf_len);
+void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable);
+int open_file(const char *filename, bool newfie, FILE **f);
+char *get_next_filename(const char *name, const char *suffix);
+void do_insertfile(
+#ifndef NANO_TINY
+ bool execute
+#else
+ void
+#endif
+ );
+void do_insertfile_void(void);
+char *get_full_path(const char *origpath);
+char *check_writable_directory(const char *path);
+char *safe_tempfile(FILE **f);
+#ifndef DISABLE_OPERATINGDIR
+void init_operating_dir(void);
+bool check_operating_dir(const char *currpath, bool allow_tabcomp);
+#endif
+#ifndef NANO_TINY
+void init_backup_dir(void);
+#endif
+int copy_file(FILE *inn, FILE *out);
+bool write_file(const char *name, FILE *f_open, bool tmp, append_type
+ append, bool nonamechange);
+#ifndef NANO_TINY
+bool write_marked_file(const char *name, FILE *f_open, bool tmp,
+ append_type append);
+#endif
+bool do_writeout(bool exiting);
+void do_writeout_void(void);
+char *real_dir_from_tilde(const char *buf);
+#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
+int diralphasort(const void *va, const void *vb);
+void free_chararray(char **array, size_t len);
+#endif
+#ifndef DISABLE_TABCOMP
+bool is_dir(const char *buf);
+char **username_tab_completion(const char *buf, size_t *num_matches,
+ size_t buf_len);
+char **cwd_tab_completion(const char *buf, bool allow_files, size_t
+ *num_matches, size_t buf_len);
+char *input_tab(char *buf, bool allow_files, size_t *place, bool
+ *lastwastab, void (*refresh_func)(void), bool *list);
+#endif
+const char *tail(const char *foo);
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+char *histfilename(void);
+void load_history(void);
+bool writehist(FILE *hist, filestruct *histhead);
+void save_history(void);
+int check_dotnano(void);
+void load_poshistory(void);
+void save_poshistory(void);
+int check_poshistory(const char *file, ssize_t *line, ssize_t *column);
+#endif
+
+/* All functions in global.c. */
+size_t length_of_list(int menu);
+#ifndef NANO_TINY
+void toggle_init_one(int val
+#ifndef DISABLE_HELP
+ , const char *desc, bool blank_after
+#endif
+ , long flag);
+void toggle_init(void);
+#endif
+void sc_init_one(shortcut **shortcutage, int ctrlval, const char *desc
+#ifndef DISABLE_HELP
+ , const char *help, bool blank_after
+#endif
+ , int metaval, int funcval, int miscval, bool view, void
+ (*func)(void));
+void shortcut_init(bool unjustify);
+void free_shortcutage(shortcut **shortcutage);
+#ifdef DEBUG
+void thanks_for_all_the_fish(void);
+#endif
+
+/* All functions in help.c. */
+#ifndef DISABLE_BROWSER
+void do_browser_help(void);
+#endif
+void do_help_void(void);
+#ifndef DISABLE_HELP
+void do_help(void (*refresh_func)(void));
+void help_init(void);
+void parse_help_input(int *kbinput, bool *meta_key, bool *func_key);
+size_t help_line_len(const char *ptr);
+#endif
+
+/* All functions in move.c. */
+void do_first_line(void);
+void do_last_line(void);
+void do_page_up(void);
+void do_page_down(void);
+#ifndef DISABLE_JUSTIFY
+void do_para_begin(bool allow_update);
+void do_para_begin_void(void);
+void do_para_end(bool allow_update);
+void do_para_end_void(void);
+#endif
+#ifndef NANO_TINY
+bool do_next_word(bool allow_punct, bool allow_update);
+void do_next_word_void(void);
+bool do_prev_word(bool allow_punct, bool allow_update);
+void do_prev_word_void(void);
+#endif
+void do_home(void);
+void do_end(void);
+void do_up(
+#ifndef NANO_TINY
+ bool scroll_only
+#else
+ void
+#endif
+ );
+void do_up_void(void);
+#ifndef NANO_TINY
+void do_scroll_up(void);
+#endif
+void do_down(
+#ifndef NANO_TINY
+ bool scroll_only
+#else
+ void
+#endif
+ );
+void do_down_void(void);
+#ifndef NANO_TINY
+void do_scroll_down(void);
+#endif
+void do_left(void);
+void do_right(void);
+
+/* All functions in nano.c. */
+filestruct *make_new_node(filestruct *prevnode);
+filestruct *copy_node(const filestruct *src);
+void splice_node(filestruct *begin, filestruct *newnode, filestruct
+ *end);
+void unlink_node(const filestruct *fileptr);
+void delete_node(filestruct *fileptr);
+filestruct *copy_filestruct(const filestruct *src);
+void free_filestruct(filestruct *src);
+void renumber(filestruct *fileptr);
+partition *partition_filestruct(filestruct *top, size_t top_x,
+ filestruct *bot, size_t bot_x);
+void unpartition_filestruct(partition **p);
+void move_to_filestruct(filestruct **file_top, filestruct **file_bot,
+ filestruct *top, size_t top_x, filestruct *bot, size_t bot_x);
+void copy_from_filestruct(filestruct *file_top, filestruct *file_bot);
+openfilestruct *make_new_opennode(void);
+void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
+ openfilestruct *end);
+void unlink_opennode(openfilestruct *fileptr);
+void delete_opennode(openfilestruct *fileptr);
+#ifdef DEBUG
+void free_openfilestruct(openfilestruct *src);
+#endif
+void print_view_warning(void);
+void finish(void);
+void die(const char *msg, ...);
+void die_save_file(const char *die_filename
+#ifndef NANO_TINY
+ , struct stat *die_stat
+#endif
+ );
+void window_init(void);
+#ifndef DISABLE_MOUSE
+void disable_mouse_support(void);
+void enable_mouse_support(void);
+void mouse_init(void);
+#endif
+void print_opt_full(const char *shortflag
+#ifdef HAVE_GETOPT_LONG
+ , const char *longflag
+#endif
+ , const char *desc);
+void usage(void);
+void version(void);
+int no_more_space(void);
+int no_help(void);
+void nano_disabled_msg(void);
+void do_exit(void);
+void signal_init(void);
+RETSIGTYPE handle_hupterm(int signal);
+RETSIGTYPE do_suspend(int signal);
+RETSIGTYPE do_continue(int signal);
+#ifndef NANO_TINY
+RETSIGTYPE handle_sigwinch(int signal);
+void allow_pending_sigwinch(bool allow);
+#endif
+#ifndef NANO_TINY
+void do_toggle(int flag);
+void do_toggle_void(void);
+#endif
+void disable_extended_io(void);
+#ifdef USE_SLANG
+void disable_signals(void);
+#endif
+#ifndef NANO_TINY
+void enable_signals(void);
+#endif
+void disable_flow_control(void);
+void enable_flow_control(void);
+void terminal_init(void);
+int do_input(bool *meta_key, bool *func_key, bool *have_shortcut, bool
+ *ran_func, bool *finished, bool allow_funcs);
+#ifndef DISABLE_MOUSE
+int do_mouse(void);
+#endif
+void do_output(char *output, size_t output_len, bool allow_cntrls);
+
+/* All functions in prompt.c. */
+int do_statusbar_input(bool *meta_key, bool *func_key, bool *have_shortcut,
+ bool *ran_func, bool *finished, bool allow_funcs, void
+ (*refresh_func)(void));
+#ifndef DISABLE_MOUSE
+int do_statusbar_mouse(void);
+#endif
+void do_statusbar_output(char *output, size_t output_len, bool
+ *got_enter, bool allow_cntrls);
+void do_statusbar_home(void);
+void do_statusbar_end(void);
+void do_statusbar_left(void);
+void do_statusbar_right(void);
+void do_statusbar_backspace(void);
+void do_statusbar_delete(void);
+void do_statusbar_cut_text(void);
+#ifndef NANO_TINY
+bool do_statusbar_next_word(bool allow_punct);
+bool do_statusbar_prev_word(bool allow_punct);
+#endif
+void do_statusbar_verbatim_input(bool *got_enter);
+#ifndef NANO_TINY
+bool find_statusbar_bracket_match(bool reverse, const char
+ *bracket_set);
+void do_statusbar_find_bracket(void);
+#endif
+size_t statusbar_xplustabs(void);
+size_t get_statusbar_page_start(size_t start_col, size_t column);
+void reset_statusbar_cursor(void);
+void update_statusbar_line(const char *curranswer, size_t index);
+bool need_statusbar_horizontal_update(size_t pww_save);
+void total_statusbar_refresh(void (*refresh_func)(void));
+const sc *get_prompt_string(int *value, bool allow_tabs,
+#ifndef DISABLE_TABCOMP
+ bool allow_files,
+#endif
+ const char *curranswer,
+ bool *meta_key, bool *func_key,
+#ifndef NANO_TINY
+ filestruct **history_list,
+#endif
+ void (*refresh_func)(void), int menu
+#ifndef DISABLE_TABCOMP
+ , bool *list
+#endif
+ );
+int do_prompt(bool allow_tabs,
+#ifndef DISABLE_TABCOMP
+ bool allow_files,
+#endif
+ int menu, const char *curranswer,
+ bool *meta_key, bool *func_key,
+#ifndef NANO_TINY
+ filestruct **history_list,
+#endif
+ void (*refresh_func)(void), const char *msg, ...);
+void do_prompt_abort(void);
+int do_yesno_prompt(bool all, const char *msg);
+
+/* All functions in rcfile.c. */
+#ifdef ENABLE_NANORC
+void rcfile_error(const char *msg, ...);
+char *parse_next_word(char *ptr);
+char *parse_argument(char *ptr);
+#ifdef ENABLE_COLOR
+char *parse_next_regex(char *ptr);
+bool nregcomp(const char *regex, int eflags);
+void parse_syntax(char *ptr);
+void parse_magic_syntax(char *ptr);
+void parse_include(char *ptr);
+short color_to_short(const char *colorname, bool *bright);
+void parse_colors(char *ptr, bool icase);
+void reset_multis(filestruct *fileptr, bool force);
+void alloc_multidata_if_needed(filestruct *fileptr);
+#endif
+void parse_rcfile(FILE *rcstream
+#ifdef ENABLE_COLOR
+ , bool syntax_only
+#endif
+ );
+void do_rcfile(void);
+#endif
+
+/* All functions in search.c. */
+#ifdef HAVE_REGEX_H
+bool regexp_init(const char *regexp);
+void regexp_cleanup(void);
+#endif
+void not_found_msg(const char *str);
+void search_replace_abort(void);
+void search_init_globals(void);
+int search_init(bool replacing, bool use_answer);
+bool findnextstr(
+#ifndef DISABLE_SPELLER
+ bool whole_word,
+#endif
+ bool no_sameline, const filestruct *begin, size_t begin_x, const
+ char *needle, size_t *needle_len);
+void findnextstr_wrap_reset(void);
+void do_search(void);
+#ifndef NANO_TINY
+void do_research(void);
+#endif
+#ifdef HAVE_REGEX_H
+int replace_regexp(char *string, bool create);
+#endif
+char *replace_line(const char *needle);
+ssize_t do_replace_loop(
+#ifndef DISABLE_SPELLER
+ bool whole_word,
+#endif
+ bool *canceled, const filestruct *real_current, size_t
+ *real_current_x, const char *needle);
+void do_replace(void);
+void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
+ bool interactive, bool save_pos, bool allow_update);
+void do_gotolinecolumn_void(void);
+#ifndef DISABLE_SPELLER
+void do_gotopos(ssize_t pos_line, size_t pos_x, ssize_t pos_y, size_t
+ pos_pww);
+#endif
+#ifndef NANO_TINY
+bool find_bracket_match(bool reverse, const char *bracket_set);
+void do_find_bracket(void);
+#ifdef ENABLE_NANORC
+bool history_has_changed(void);
+#endif
+void history_init(void);
+void history_reset(const filestruct *h);
+filestruct *find_history(const filestruct *h_start, const filestruct
+ *h_end, const char *s, size_t len);
+void update_history(filestruct **h, const char *s);
+char *get_history_older(filestruct **h);
+char *get_history_newer(filestruct **h);
+void get_history_older_void(void);
+void get_history_newer_void(void);
+#ifndef DISABLE_TABCOMP
+char *get_history_completion(filestruct **h, const char *s, size_t len);
+#endif
+#endif
+
+/* All functions in text.c. */
+#ifndef NANO_TINY
+void do_mark(void);
+#endif
+void do_delete(void);
+void do_backspace(void);
+void do_tab(void);
+#ifndef NANO_TINY
+void do_indent(ssize_t cols);
+void do_indent_void(void);
+void do_unindent(void);
+void do_undo(void);
+void do_redo(void);
+#endif
+void do_enter(bool undoing);
+void do_enter_void(void);
+#ifndef NANO_TINY
+RETSIGTYPE cancel_command(int signal);
+bool execute_command(const char *command);
+#endif
+#ifndef DISABLE_WRAPPING
+void wrap_reset(void);
+bool do_wrap(filestruct *line, bool undoing);
+#endif
+#if !defined(DISABLE_HELP) || !defined(DISABLE_WRAPJUSTIFY)
+ssize_t break_line(const char *line, ssize_t goal
+#ifndef DISABLE_HELP
+ , bool newln
+#endif
+ );
+#endif
+#if !defined(NANO_TINY) || !defined(DISABLE_JUSTIFY)
+size_t indent_length(const char *line);
+#endif
+#ifndef DISABLE_JUSTIFY
+void justify_format(filestruct *paragraph, size_t skip);
+size_t quote_length(const char *line);
+bool quotes_match(const char *a_line, size_t a_quote, const char
+ *b_line);
+bool indents_match(const char *a_line, size_t a_indent, const char
+ *b_line, size_t b_indent);
+bool begpar(const filestruct *const foo);
+bool inpar(const filestruct *const foo);
+void backup_lines(filestruct *first_line, size_t par_len);
+bool find_paragraph(size_t *const quote, size_t *const par);
+void do_justify(bool full_justify);
+void do_justify_void(void);
+void do_full_justify(void);
+#endif
+#ifndef DISABLE_SPELLER
+bool do_int_spell_fix(const char *word);
+const char *do_int_speller(const char *tempfile_name);
+const char *do_alt_speller(char *tempfile_name);
+void do_spell(void);
+#endif
+#ifndef NANO_TINY
+void do_wordlinechar_count(void);
+#endif
+void do_verbatim_input(void);
+
+/* All functions in utils.c. */
+int digits(size_t n);
+void get_homedir(void);
+bool parse_num(const char *str, ssize_t *val);
+bool parse_line_column(const char *str, ssize_t *line, ssize_t *column);
+void align(char **str);
+void null_at(char **data, size_t index);
+void unsunder(char *str, size_t true_len);
+void sunder(char *str);
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+#ifndef HAVE_GETLINE
+ssize_t ngetline(char **lineptr, size_t *n, FILE *stream);
+#endif
+#ifndef HAVE_GETDELIM
+ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream);
+#endif
+#endif
+#ifdef HAVE_REGEX_H
+bool regexp_bol_or_eol(const regex_t *preg, const char *string);
+const char *fixbounds(const char *r);
+#endif
+#ifndef DISABLE_SPELLER
+bool is_whole_word(size_t pos, const char *buf, const char *word);
+#endif
+const char *strstrwrapper(const char *haystack, const char *needle,
+ const char *start);
+void nperror(const char *s);
+void *nmalloc(size_t howmuch);
+void *nrealloc(void *ptr, size_t howmuch);
+char *mallocstrncpy(char *dest, const char *src, size_t n);
+char *mallocstrcpy(char *dest, const char *src);
+char *mallocstrassn(char *dest, char *src);
+size_t get_page_start(size_t column);
+size_t xplustabs(void);
+size_t actual_x(const char *s, size_t column);
+size_t strnlenpt(const char *s, size_t maxlen);
+size_t strlenpt(const char *s);
+void new_magicline(void);
+#ifndef NANO_TINY
+void remove_magicline(void);
+void mark_order(const filestruct **top, size_t *top_x, const filestruct
+ **bot, size_t *bot_x, bool *right_side_up);
+void add_undo(undo_type current_action);
+void update_undo(undo_type action);
+#endif
+size_t get_totsize(const filestruct *begin, const filestruct *end);
+filestruct *fsfromline(ssize_t lineno);
+#ifdef DEBUG
+void dump_filestruct(const filestruct *inptr);
+void dump_filestruct_reverse(void);
+#endif
+
+/* All functions in winio.c. */
+void get_key_buffer(WINDOW *win);
+size_t get_key_buffer_len(void);
+void unget_input(int *input, size_t input_len);
+void unget_kbinput(int kbinput, bool meta_key, bool func_key);
+int *get_input(WINDOW *win, size_t input_len);
+int get_kbinput(WINDOW *win, bool *meta_key, bool *func_key);
+int parse_kbinput(WINDOW *win, bool *meta_key, bool *func_key);
+int get_escape_seq_kbinput(const int *seq, size_t seq_len);
+int get_escape_seq_abcd(int kbinput);
+int parse_escape_seq_kbinput(WINDOW *win, int kbinput);
+int get_byte_kbinput(int kbinput);
+#ifdef ENABLE_UTF8
+long add_unicode_digit(int kbinput, long factor, long *uni);
+long get_unicode_kbinput(int kbinput);
+#endif
+int get_control_kbinput(int kbinput);
+void unparse_kbinput(char *output, size_t output_len);
+int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len);
+int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len);
+#ifndef DISABLE_MOUSE
+int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts);
+#endif
+const sc *get_shortcut(int menu, int *kbinput, bool
+ *meta_key, bool *func_key);
+const sc *first_sc_for(int menu, void (*func)(void));
+void blank_line(WINDOW *win, int y, int x, int n);
+void blank_titlebar(void);
+void blank_topbar(void);
+void blank_edit(void);
+void blank_statusbar(void);
+void blank_bottombars(void);
+void check_statusblank(void);
+char *display_string(const char *buf, size_t start_col, size_t len, bool
+ dollars);
+void titlebar(const char *path);
+void set_modified(void);
+void statusbar(const char *msg, ...);
+void bottombars(int menu);
+void onekey(const char *keystroke, const char *desc, size_t len);
+void reset_cursor(void);
+void edit_draw(filestruct *fileptr, const char *converted, int
+ line, size_t start);
+int update_line(filestruct *fileptr, size_t index);
+bool need_horizontal_update(size_t pww_save);
+bool need_vertical_update(size_t pww_save);
+void edit_scroll(scroll_dir direction, ssize_t nlines);
+void edit_redraw(filestruct *old_current, size_t pww_save);
+void edit_refresh(void);
+void edit_update(update_type location);
+void total_redraw(void);
+void total_refresh(void);
+void display_main_list(void);
+void do_cursorpos(bool constant);
+void do_cursorpos_void(void);
+void do_replace_highlight(bool highlight, const char *word);
+const char *flagtostr(int flag);
+const subnfunc *sctofunc(sc *s);
+const subnfunc *getfuncfromkey(WINDOW *win);
+void print_sclist(void);
+sc *strtosc(int menu, char *input);
+function_type strtokeytype(const char *str);
+int strtomenu(char *input);
+void assign_keyinfo(sc *s);
+void xon_complaint(void);
+void xoff_complaint(void);
+int sc_seq_or (void (*func)(void), int defaultval);
+void do_suspend_void(void);
+
+extern const char *cancel_msg;
+#ifndef NANO_TINY
+extern const char *case_sens_msg;
+extern const char *backwards_msg;
+extern const char *prev_history_msg;
+extern const char *next_history_msg;
+#endif
+extern const char *replace_msg;
+extern const char *no_replace_msg;
+extern const char *go_to_line_msg;
+extern const char *whereis_next_msg;
+extern const char *first_file_msg;
+extern const char *last_file_msg;
+extern const char *goto_dir_msg;
+extern const char *ext_cmd_msg;
+extern const char *to_files_msg;
+extern const char *dos_format_msg;
+extern const char *mac_format_msg;
+extern const char *append_msg;
+extern const char *prepend_msg;
+extern const char *backup_file_msg;
+extern const char *gototext_msg;
+extern const char *new_buffer_msg;
+
+void enable_nodelay(void);
+void disable_nodelay(void);
+
+#ifdef HAVE_REGEX_H
+extern const char *regexp_msg;
+#endif
+
+#ifdef NANO_EXTRA
+void do_credits(void);
+#endif
+
+/* May as just throw these here since they are just placeholders */
+void do_cancel(void);
+void case_sens_void(void);
+void regexp_void(void);
+void gototext_void(void);
+void to_files_void(void);
+void dos_format_void(void);
+void mac_format_void(void);
+void append_void(void);
+void prepend_void(void);
+void backup_file_void(void);
+void new_buffer_void(void);
+void backwards_void(void);
+void goto_dir_void(void);
+void no_replace_void(void);
+void ext_cmd_void(void);
+
+
+#endif /* !PROTO_H */
diff --git a/src/rcfile.c b/src/rcfile.c
new file mode 100644
index 0000000..f5e507a
--- /dev/null
+++ b/src/rcfile.c
@@ -0,0 +1,1305 @@
+/* $Id: rcfile.c 4530 2011-02-18 07:30:57Z astyanax $ */
+/**************************************************************************
+ * rcfile.c *
+ * *
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 *
+ * Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#ifdef ENABLE_NANORC
+
+static const rcoption rcopts[] = {
+ {"boldtext", BOLD_TEXT},
+#ifndef DISABLE_JUSTIFY
+ {"brackets", 0},
+#endif
+ {"const", CONST_UPDATE},
+#ifndef DISABLE_WRAPJUSTIFY
+ {"fill", 0},
+#endif
+#ifndef DISABLE_MOUSE
+ {"mouse", USE_MOUSE},
+#endif
+#ifdef ENABLE_MULTIBUFFER
+ {"multibuffer", MULTIBUFFER},
+#endif
+ {"morespace", MORE_SPACE},
+ {"nofollow", NOFOLLOW_SYMLINKS},
+ {"nohelp", NO_HELP},
+ {"nonewlines", NO_NEWLINES},
+#ifndef DISABLE_WRAPPING
+ {"nowrap", NO_WRAP},
+#endif
+#ifndef DISABLE_OPERATINGDIR
+ {"operatingdir", 0},
+#endif
+ {"preserve", PRESERVE},
+#ifndef DISABLE_JUSTIFY
+ {"punct", 0},
+ {"quotestr", 0},
+#endif
+ {"rebinddelete", REBIND_DELETE},
+ {"rebindkeypad", REBIND_KEYPAD},
+#ifdef HAVE_REGEX_H
+ {"regexp", USE_REGEXP},
+#endif
+#ifndef DISABLE_SPELLER
+ {"speller", 0},
+#endif
+ {"suspend", SUSPEND},
+ {"tabsize", 0},
+ {"tempfile", TEMP_FILE},
+ {"view", VIEW_MODE},
+#ifndef NANO_TINY
+ {"autoindent", AUTOINDENT},
+ {"backup", BACKUP_FILE},
+ {"allow_insecure_backup", INSECURE_BACKUP},
+ {"backupdir", 0},
+ {"backwards", BACKWARDS_SEARCH},
+ {"casesensitive", CASE_SENSITIVE},
+ {"cut", CUT_TO_END},
+ {"historylog", HISTORYLOG},
+ {"matchbrackets", 0},
+ {"noconvert", NO_CONVERT},
+ {"poslog", POS_HISTORY},
+ {"quiet", QUIET},
+ {"quickblank", QUICK_BLANK},
+ {"smarthome", SMART_HOME},
+ {"smooth", SMOOTH_SCROLL},
+ {"tabstospaces", TABS_TO_SPACES},
+ {"undo", UNDOABLE},
+ {"whitespace", 0},
+ {"wordbounds", WORD_BOUNDS},
+ {"softwrap", SOFTWRAP},
+#endif
+ {NULL, 0}
+};
+
+static bool errors = FALSE;
+ /* Whether we got any errors while parsing an rcfile. */
+static size_t lineno = 0;
+ /* If we did, the line number where the last error occurred. */
+static char *nanorc = NULL;
+ /* The path to the rcfile we're parsing. */
+#ifdef ENABLE_COLOR
+static syntaxtype *endsyntax = NULL;
+ /* The end of the list of syntaxes. */
+static exttype *endheader = NULL;
+ /* End of header list */
+static colortype *endcolor = NULL;
+ /* The end of the color list for the current syntax. */
+
+#endif
+
+/* We have an error in some part of the rcfile. Print the error message
+ * on stderr, and then make the user hit Enter to continue starting
+ * nano. */
+void rcfile_error(const char *msg, ...)
+{
+ va_list ap;
+
+ if (ISSET(QUIET))
+ return;
+
+ fprintf(stderr, "\n");
+ if (lineno > 0) {
+ errors = TRUE;
+ fprintf(stderr, _("Error in %s on line %lu: "), nanorc, (unsigned long)lineno);
+ }
+
+ va_start(ap, msg);
+ vfprintf(stderr, _(msg), ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+/* Parse the next word from the string, null-terminate it, and return
+ * a pointer to the first character after the null terminator. The
+ * returned pointer will point to '\0' if we hit the end of the line. */
+char *parse_next_word(char *ptr)
+{
+ while (!isblank(*ptr) && *ptr != '\0')
+ ptr++;
+
+ if (*ptr == '\0')
+ return ptr;
+
+ /* Null-terminate and advance ptr. */
+ *ptr++ = '\0';
+
+ while (isblank(*ptr))
+ ptr++;
+
+ return ptr;
+}
+
+/* Parse an argument, with optional quotes, after a keyword that takes
+ * one. If the next word starts with a ", we say that it ends with the
+ * last " of the line. Otherwise, we interpret it as usual, so that the
+ * arguments can contain "'s too. */
+char *parse_argument(char *ptr)
+{
+ const char *ptr_save = ptr;
+ char *last_quote = NULL;
+
+ assert(ptr != NULL);
+
+ if (*ptr != '"')
+ return parse_next_word(ptr);
+
+ do {
+ ptr++;
+ if (*ptr == '"')
+ last_quote = ptr;
+ } while (*ptr != '\0');
+
+ if (last_quote == NULL) {
+ if (*ptr == '\0')
+ ptr = NULL;
+ else
+ *ptr++ = '\0';
+ rcfile_error(N_("Argument '%s' has an unterminated \""), ptr_save);
+ } else {
+ *last_quote = '\0';
+ ptr = last_quote + 1;
+ }
+ if (ptr != NULL)
+ while (isblank(*ptr))
+ ptr++;
+ return ptr;
+}
+
+#ifdef ENABLE_COLOR
+/* Parse the next regex string from the line at ptr, and return it. */
+char *parse_next_regex(char *ptr)
+{
+ assert(ptr != NULL);
+
+ /* Continue until the end of the line, or a " followed by a space, a
+ * blank character, or \0. */
+ while ((*ptr != '"' || (!isblank(*(ptr + 1)) &&
+ *(ptr + 1) != '\0')) && *ptr != '\0')
+ ptr++;
+
+ assert(*ptr == '"' || *ptr == '\0');
+
+ if (*ptr == '\0') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ return NULL;
+ }
+
+ /* Null-terminate and advance ptr. */
+ *ptr++ = '\0';
+
+ while (isblank(*ptr))
+ ptr++;
+
+ return ptr;
+}
+
+/* Compile the regular expression regex to see if it's valid. Return
+ * TRUE if it is, or FALSE otherwise. */
+bool nregcomp(const char *regex, int eflags)
+{
+ regex_t preg;
+ const char *r = fixbounds(regex);
+ int rc = regcomp(&preg, r, REG_EXTENDED | eflags);
+
+ if (rc != 0) {
+ size_t len = regerror(rc, &preg, NULL, 0);
+ char *str = charalloc(len);
+
+ regerror(rc, &preg, str, len);
+ rcfile_error(N_("Bad regex \"%s\": %s"), r, str);
+ free(str);
+ }
+
+ regfree(&preg);
+ return (rc == 0);
+}
+
+/* Parse the next syntax string from the line at ptr, and add it to the
+ * global list of color syntaxes. */
+void parse_syntax(char *ptr)
+{
+ const char *fileregptr = NULL, *nameptr = NULL;
+ syntaxtype *tmpsyntax;
+ exttype *endext = NULL;
+ /* The end of the extensions list for this syntax. */
+
+ assert(ptr != NULL);
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing syntax name"));
+ return;
+ }
+
+ if (*ptr != '"') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ return;
+ }
+
+ ptr++;
+
+ nameptr = ptr;
+ ptr = parse_next_regex(ptr);
+
+ if (ptr == NULL)
+ return;
+
+ /* Search for a duplicate syntax name. If we find one, free it, so
+ * that we always use the last syntax with a given name. */
+ for (tmpsyntax = syntaxes; tmpsyntax != NULL;
+ tmpsyntax = tmpsyntax->next) {
+ if (strcmp(nameptr, tmpsyntax->desc) == 0) {
+ syntaxtype *prev_syntax = tmpsyntax;
+
+ tmpsyntax = tmpsyntax->next;
+ free(prev_syntax);
+ break;
+ }
+ }
+
+ if (syntaxes == NULL) {
+ syntaxes = (syntaxtype *)nmalloc(sizeof(syntaxtype));
+ endsyntax = syntaxes;
+ } else {
+ endsyntax->next = (syntaxtype *)nmalloc(sizeof(syntaxtype));
+ endsyntax = endsyntax->next;
+#ifdef DEBUG
+ fprintf(stderr, "Adding new syntax after first one\n");
+#endif
+ }
+
+ endsyntax->desc = mallocstrcpy(NULL, nameptr);
+ endsyntax->color = NULL;
+ endcolor = NULL;
+ endheader = NULL;
+ endsyntax->extensions = NULL;
+ endsyntax->headers = NULL;
+ endsyntax->magics = NULL;
+ endsyntax->next = NULL;
+ endsyntax->nmultis = 0;
+
+#ifdef DEBUG
+ fprintf(stderr, "Starting a new syntax type: \"%s\"\n", nameptr);
+#endif
+
+ /* The "none" syntax is the same as not having a syntax at all, so
+ * we can't assign any extensions or colors to it. */
+ if (strcmp(endsyntax->desc, "none") == 0) {
+ rcfile_error(N_("The \"none\" syntax is reserved"));
+ return;
+ }
+
+ /* The default syntax should have no associated extensions. */
+ if (strcmp(endsyntax->desc, "default") == 0 && *ptr != '\0') {
+ rcfile_error(
+ N_("The \"default\" syntax must take no extensions"));
+ return;
+ }
+
+ /* Now load the extensions into their part of the struct. */
+ while (*ptr != '\0') {
+ exttype *newext;
+ /* The new extension structure. */
+
+ while (*ptr != '"' && *ptr != '\0')
+ ptr++;
+
+ if (*ptr == '\0')
+ return;
+
+ ptr++;
+
+ fileregptr = ptr;
+ ptr = parse_next_regex(ptr);
+ if (ptr == NULL)
+ break;
+
+ newext = (exttype *)nmalloc(sizeof(exttype));
+
+ /* Save the extension regex if it's valid. */
+ if (nregcomp(fileregptr, REG_NOSUB)) {
+ newext->ext_regex = mallocstrcpy(NULL, fileregptr);
+ newext->ext = NULL;
+
+ if (endext == NULL)
+ endsyntax->extensions = newext;
+ else
+ endext->next = newext;
+ endext = newext;
+ endext->next = NULL;
+ } else
+ free(newext);
+ }
+
+}
+
+
+/* Parse the next syntax string from the line at ptr, and add it to the
+ * global list of color syntaxes. */
+void parse_magictype(char *ptr)
+{
+#ifdef HAVE_LIBMAGIC
+ const char *fileregptr = NULL;
+ exttype *endext = NULL;
+
+ assert(ptr != NULL);
+
+ if (syntaxes == NULL) {
+ rcfile_error(
+ N_("Cannot add a magic string regex without a syntax command"));
+ return;
+ }
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing magic string name"));
+ return;
+ }
+
+ if (*ptr != '"') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ return;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Starting a magic type: \"%s\"\n", ptr);
+#endif
+
+ /* Now load the extensions into their part of the struct. */
+ while (*ptr != '\0') {
+ exttype *newext;
+ /* The new extension structure. */
+
+ while (*ptr != '"' && *ptr != '\0')
+ ptr++;
+
+ if (*ptr == '\0')
+ return;
+
+ ptr++;
+
+ fileregptr = ptr;
+ ptr = parse_next_regex(ptr);
+ if (ptr == NULL)
+ break;
+
+ newext = (exttype *)nmalloc(sizeof(exttype));
+
+ /* Save the regex if it's valid. */
+ if (nregcomp(fileregptr, REG_NOSUB)) {
+ newext->ext_regex = mallocstrcpy(NULL, fileregptr);
+ newext->ext = NULL;
+
+ if (endext == NULL)
+ endsyntax->magics = newext;
+ else
+ endext->next = newext;
+ endext = newext;
+ endext->next = NULL;
+ } else
+ free(newext);
+ }
+#endif /* HAVE_LIBMAGIC */
+}
+
+int check_bad_binding(sc *s)
+{
+#define BADLISTLEN 1
+ int badtypes[BADLISTLEN] = {META};
+ int badseqs[BADLISTLEN] = { 91 };
+ int i;
+
+ for (i = 0; i < BADLISTLEN; i++)
+ if (s->type == badtypes[i] && s->seq == badseqs[i])
+ return 1;
+
+ return 0;
+}
+
+void parse_keybinding(char *ptr)
+{
+ char *keyptr = NULL, *keycopy = NULL, *funcptr = NULL, *menuptr = NULL;
+ sc *s, *newsc;
+ int i, menu;
+
+ assert(ptr != NULL);
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing key name"));
+ return;
+ }
+
+ keyptr = ptr;
+ ptr = parse_next_word(ptr);
+ keycopy = mallocstrcpy(NULL, keyptr);
+ for (i = 0; i < strlen(keycopy); i++)
+ keycopy[i] = toupper(keycopy[i]);
+
+ if (keycopy[0] != 'M' && keycopy[0] != '^' && keycopy[0] != 'F' && keycopy[0] != 'K') {
+ rcfile_error(
+ N_("keybindings must begin with \"^\", \"M\", or \"F\""));
+ return;
+ }
+
+ funcptr = ptr;
+ ptr = parse_next_word(ptr);
+
+ if (!strcmp(funcptr, "")) {
+ rcfile_error(
+ N_("Must specify function to bind key to"));
+ return;
+ }
+
+ menuptr = ptr;
+ ptr = parse_next_word(ptr);
+
+ if (!strcmp(menuptr, "")) {
+ rcfile_error(
+ /* Note to translators, do not translate the word "all"
+ in the sentence below, everything else is fine */
+ N_("Must specify menu to bind key to (or \"all\")"));
+ return;
+ }
+
+ menu = strtomenu(menuptr);
+ newsc = strtosc(menu, funcptr);
+ if (newsc == NULL) {
+ rcfile_error(
+ N_("Could not map name \"%s\" to a function"), funcptr);
+ return;
+ }
+
+ if (menu < 1) {
+ rcfile_error(
+ N_("Could not map name \"%s\" to a menu"), menuptr);
+ return;
+ }
+
+
+#ifdef DEBUG
+ fprintf(stderr, "newsc now address %d, menu func assigned = %d, menu = %d\n",
+ &newsc, newsc->scfunc, menu);
+#endif
+
+
+ newsc->keystr = keycopy;
+ newsc->menu = menu;
+ newsc->type = strtokeytype(newsc->keystr);
+ assign_keyinfo(newsc);
+#ifdef DEBUG
+ fprintf(stderr, "s->keystr = \"%s\"\n", newsc->keystr);
+ fprintf(stderr, "s->seq = \"%d\"\n", newsc->seq);
+#endif
+
+ if (check_bad_binding(newsc)) {
+ rcfile_error(
+ N_("Sorry, keystr \"%s\" is an illegal binding"), newsc->keystr);
+ return;
+ }
+
+ /* now let's have some fun. Try and delete the other entries
+ we found for the same menu, then make this new new
+ beginning */
+ for (s = sclist; s != NULL; s = s->next) {
+ if (((s->menu & newsc->menu)) && s->seq == newsc->seq) {
+ s->menu &= ~newsc->menu;
+#ifdef DEBUG
+ fprintf(stderr, "replaced menu entry %d\n", s->menu);
+#endif
+ }
+ }
+ newsc->next = sclist;
+ sclist = newsc;
+}
+
+/* Let user unbind a sequence from a given (or all) menus */
+void parse_unbinding(char *ptr)
+{
+ char *keyptr = NULL, *keycopy = NULL, *menuptr = NULL;
+ sc *s;
+ int i, menu;
+
+ assert(ptr != NULL);
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing key name"));
+ return;
+ }
+
+ keyptr = ptr;
+ ptr = parse_next_word(ptr);
+ keycopy = mallocstrcpy(NULL, keyptr);
+ for (i = 0; i < strlen(keycopy); i++)
+ keycopy[i] = toupper(keycopy[i]);
+
+#ifdef DEBUG
+ fprintf(stderr, "Starting unbinding code");
+#endif
+
+ if (keycopy[0] != 'M' && keycopy[0] != '^' && keycopy[0] != 'F' && keycopy[0] != 'K') {
+ rcfile_error(
+ N_("keybindings must begin with \"^\", \"M\", or \"F\""));
+ return;
+ }
+
+ menuptr = ptr;
+ ptr = parse_next_word(ptr);
+
+ if (!strcmp(menuptr, "")) {
+ rcfile_error(
+ /* Note to translators, do not translate the word "all"
+ in the sentence below, everything else is fine */
+ N_("Must specify menu to bind key to (or \"all\")"));
+ return;
+ }
+
+ menu = strtomenu(menuptr);
+ if (menu < 1) {
+ rcfile_error(
+ N_("Could not map name \"%s\" to a menu"), menuptr);
+ return;
+ }
+
+
+#ifdef DEBUG
+ fprintf(stderr, "unbinding \"%s\" from menu = %d\n", keycopy, menu);
+#endif
+
+ /* Now find the apropriate entries in the menu to delete */
+ for (s = sclist; s != NULL; s = s->next) {
+ if (((s->menu & menu)) && !strcmp(s->keystr,keycopy)) {
+ s->menu &= ~menu;
+#ifdef DEBUG
+ fprintf(stderr, "deleted menu entry %d\n", s->menu);
+#endif
+ }
+ }
+}
+
+
+/* Read and parse additional syntax files. */
+void parse_include(char *ptr)
+{
+ struct stat rcinfo;
+ FILE *rcstream;
+ char *option, *nanorc_save = nanorc, *expanded;
+ size_t lineno_save = lineno;
+
+ option = ptr;
+ if (*option == '"')
+ option++;
+ ptr = parse_argument(ptr);
+
+ /* Can't get the specified file's full path cause it may screw up
+ our cwd depending on the parent dirs' permissions, (see Savannah bug 25297) */
+
+ /* Don't open directories, character files, or block files. */
+ if (stat(option, &rcinfo) != -1) {
+ if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
+ S_ISBLK(rcinfo.st_mode)) {
+ rcfile_error(S_ISDIR(rcinfo.st_mode) ?
+ _("\"%s\" is a directory") :
+ _("\"%s\" is a device file"), option);
+ }
+ }
+
+ expanded = real_dir_from_tilde(option);
+
+ /* Open the new syntax file. */
+ if ((rcstream = fopen(expanded, "rb")) == NULL) {
+ rcfile_error(_("Error reading %s: %s"), expanded,
+ strerror(errno));
+ return;
+ }
+
+ /* Use the name and line number position of the new syntax file
+ * while parsing it, so we can know where any errors in it are. */
+ nanorc = expanded;
+ lineno = 0;
+
+#ifdef DEBUG
+ fprintf(stderr, "Parsing file \"%s\" (expanded from \"%s\")\n", expanded, option);
+#endif
+
+ parse_rcfile(rcstream
+#ifdef ENABLE_COLOR
+ , TRUE
+#endif
+ );
+
+ /* We're done with the new syntax file. Restore the original
+ * filename and line number position. */
+ nanorc = nanorc_save;
+ lineno = lineno_save;
+
+}
+
+/* Return the short value corresponding to the color named in colorname,
+ * and set bright to TRUE if that color is bright. */
+short color_to_short(const char *colorname, bool *bright)
+{
+ short mcolor = -1;
+
+ assert(colorname != NULL && bright != NULL);
+
+ if (strncasecmp(colorname, "bright", 6) == 0) {
+ *bright = TRUE;
+ colorname += 6;
+ }
+
+ if (strcasecmp(colorname, "green") == 0)
+ mcolor = COLOR_GREEN;
+ else if (strcasecmp(colorname, "red") == 0)
+ mcolor = COLOR_RED;
+ else if (strcasecmp(colorname, "blue") == 0)
+ mcolor = COLOR_BLUE;
+ else if (strcasecmp(colorname, "white") == 0)
+ mcolor = COLOR_WHITE;
+ else if (strcasecmp(colorname, "yellow") == 0)
+ mcolor = COLOR_YELLOW;
+ else if (strcasecmp(colorname, "cyan") == 0)
+ mcolor = COLOR_CYAN;
+ else if (strcasecmp(colorname, "magenta") == 0)
+ mcolor = COLOR_MAGENTA;
+ else if (strcasecmp(colorname, "black") == 0)
+ mcolor = COLOR_BLACK;
+ else
+ rcfile_error(N_("Color \"%s\" not understood.\n"
+ "Valid colors are \"green\", \"red\", \"blue\",\n"
+ "\"white\", \"yellow\", \"cyan\", \"magenta\" and\n"
+ "\"black\", with the optional prefix \"bright\"\n"
+ "for foreground colors."), colorname);
+
+ return mcolor;
+}
+
+/* Parse the color string in the line at ptr, and add it to the current
+ * file's associated colors. If icase is TRUE, treat the color string
+ * as case insensitive. */
+void parse_colors(char *ptr, bool icase)
+{
+ short fg, bg;
+ bool bright = FALSE, no_fgcolor = FALSE;
+ char *fgstr;
+
+ assert(ptr != NULL);
+
+ if (syntaxes == NULL) {
+ rcfile_error(
+ N_("Cannot add a color command without a syntax command"));
+ return;
+ }
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing color name"));
+ return;
+ }
+
+ fgstr = ptr;
+ ptr = parse_next_word(ptr);
+
+ if (strchr(fgstr, ',') != NULL) {
+ char *bgcolorname;
+
+ strtok(fgstr, ",");
+ bgcolorname = strtok(NULL, ",");
+ if (bgcolorname == NULL) {
+ /* If we have a background color without a foreground color,
+ * parse it properly. */
+ bgcolorname = fgstr + 1;
+ no_fgcolor = TRUE;
+ }
+ if (strncasecmp(bgcolorname, "bright", 6) == 0) {
+ rcfile_error(
+ N_("Background color \"%s\" cannot be bright"),
+ bgcolorname);
+ return;
+ }
+ bg = color_to_short(bgcolorname, &bright);
+ } else
+ bg = -1;
+
+ if (!no_fgcolor) {
+ fg = color_to_short(fgstr, &bright);
+
+ /* Don't try to parse screwed-up foreground colors. */
+ if (fg == -1)
+ return;
+ } else
+ fg = -1;
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing regex string"));
+ return;
+ }
+
+ /* Now for the fun part. Start adding regexes to individual strings
+ * in the colorstrings array, woo! */
+ while (ptr != NULL && *ptr != '\0') {
+ colortype *newcolor;
+ /* The new color structure. */
+ bool cancelled = FALSE;
+ /* The start expression was bad. */
+ bool expectend = FALSE;
+ /* Do we expect an end= line? */
+
+ if (strncasecmp(ptr, "start=", 6) == 0) {
+ ptr += 6;
+ expectend = TRUE;
+ }
+
+ if (*ptr != '"') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ ptr = parse_next_regex(ptr);
+ continue;
+ }
+
+ ptr++;
+
+ fgstr = ptr;
+ ptr = parse_next_regex(ptr);
+ if (ptr == NULL)
+ break;
+
+ newcolor = (colortype *)nmalloc(sizeof(colortype));
+
+ /* Save the starting regex string if it's valid, and set up the
+ * color information. */
+ if (nregcomp(fgstr, icase ? REG_ICASE : 0)) {
+ newcolor->fg = fg;
+ newcolor->bg = bg;
+ newcolor->bright = bright;
+ newcolor->icase = icase;
+
+ newcolor->start_regex = mallocstrcpy(NULL, fgstr);
+ newcolor->start = NULL;
+
+ newcolor->end_regex = NULL;
+ newcolor->end = NULL;
+
+ newcolor->next = NULL;
+
+ if (endcolor == NULL) {
+ endsyntax->color = newcolor;
+#ifdef DEBUG
+ fprintf(stderr, "Starting a new colorstring for fg %hd, bg %hd\n", fg, bg);
+#endif
+ } else {
+#ifdef DEBUG
+ fprintf(stderr, "Adding new entry for fg %hd, bg %hd\n", fg, bg);
+#endif
+ endcolor->next = newcolor;
+ }
+
+ endcolor = newcolor;
+ } else {
+ free(newcolor);
+ cancelled = TRUE;
+ }
+
+ if (expectend) {
+ if (ptr == NULL || strncasecmp(ptr, "end=", 4) != 0) {
+ rcfile_error(
+ N_("\"start=\" requires a corresponding \"end=\""));
+ return;
+ }
+ ptr += 4;
+ if (*ptr != '"') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ continue;
+ }
+
+ ptr++;
+
+ fgstr = ptr;
+ ptr = parse_next_regex(ptr);
+ if (ptr == NULL)
+ break;
+
+ /* If the start regex was invalid, skip past the end regex to
+ * stay in sync. */
+ if (cancelled)
+ continue;
+
+ /* Save the ending regex string if it's valid. */
+ newcolor->end_regex = (nregcomp(fgstr, icase ? REG_ICASE :
+ 0)) ? mallocstrcpy(NULL, fgstr) : NULL;
+
+ /* Lame way to skip another static counter */
+ newcolor->id = endsyntax->nmultis;
+ endsyntax->nmultis++;
+ }
+ }
+}
+
+/* Parse the headers (1st line) of the file which may influence the regex used. */
+void parse_headers(char *ptr)
+{
+ char *regstr;
+
+ assert(ptr != NULL);
+
+ if (syntaxes == NULL) {
+ rcfile_error(
+ N_("Cannot add a header regex without a syntax command"));
+ return;
+ }
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing regex string"));
+ return;
+ }
+
+ /* Now for the fun part. Start adding regexes to individual strings
+ * in the colorstrings array, woo! */
+ while (ptr != NULL && *ptr != '\0') {
+ exttype *newheader;
+ /* The new color structure. */
+
+ if (*ptr != '"') {
+ rcfile_error(
+ N_("Regex strings must begin and end with a \" character"));
+ ptr = parse_next_regex(ptr);
+ continue;
+ }
+
+ ptr++;
+
+ regstr = ptr;
+ ptr = parse_next_regex(ptr);
+ if (ptr == NULL)
+ break;
+
+ newheader = (exttype *)nmalloc(sizeof(exttype));
+
+ /* Save the regex string if it's valid */
+ if (nregcomp(regstr, 0)) {
+ newheader->ext_regex = mallocstrcpy(NULL, regstr);
+ newheader->ext = NULL;
+ newheader->next = NULL;
+
+#ifdef DEBUG
+ fprintf(stderr, "Starting a new header entry: %s\n", newheader->ext_regex);
+#endif
+
+ if (endheader == NULL) {
+ endsyntax->headers = newheader;
+ } else {
+ endheader->next = newheader;
+ }
+
+ endheader = newheader;
+ } else
+ free(newheader);
+
+ }
+}
+#endif /* ENABLE_COLOR */
+
+/* Check whether the user has unmapped every shortcut for a
+sequence we consider 'vital', like the exit function */
+static void check_vitals_mapped(void)
+{
+ subnfunc *f;
+ int v;
+#define VITALS 5
+ void (*vitals[VITALS])(void) = { do_exit, do_exit, do_cancel, do_cancel, do_cancel };
+ int inmenus[VITALS] = { MMAIN, MHELP, MWHEREIS, MREPLACE, MGOTOLINE };
+
+ for (v = 0; v < VITALS; v++) {
+ for (f = allfuncs; f != NULL; f = f->next) {
+ if (f->scfunc == vitals[v] && f->menus & inmenus[v]) {
+ const sc *s = first_sc_for(inmenus[v], f->scfunc);
+ if (!s) {
+ rcfile_error(N_("Fatal error: no keys mapped for function \"%s\""),
+ f->desc);
+ fprintf(stderr, N_("Exiting. Please use nano with the -I option if needed to adjust your nanorc settings\n"));
+ exit(1);
+ }
+ break;
+ }
+ }
+ }
+}
+
+/* Parse the rcfile, once it has been opened successfully at rcstream,
+ * and close it afterwards. If syntax_only is TRUE, only allow the file
+ * to contain color syntax commands: syntax, color, and icolor. */
+void parse_rcfile(FILE *rcstream
+#ifdef ENABLE_COLOR
+ , bool syntax_only
+#endif
+ )
+{
+ char *buf = NULL;
+ ssize_t len;
+ size_t n = 0;
+
+ while ((len = getline(&buf, &n, rcstream)) > 0) {
+ char *ptr, *keyword, *option;
+ int set = 0;
+ size_t i;
+
+ /* Ignore the newline. */
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ lineno++;
+ ptr = buf;
+ while (isblank(*ptr))
+ ptr++;
+
+ /* If we have a blank line or a comment, skip to the next
+ * line. */
+ if (*ptr == '\0' || *ptr == '#')
+ continue;
+
+ /* Otherwise, skip to the next space. */
+ keyword = ptr;
+ ptr = parse_next_word(ptr);
+
+ /* Try to parse the keyword. */
+ if (strcasecmp(keyword, "set") == 0) {
+#ifdef ENABLE_COLOR
+ if (syntax_only)
+ rcfile_error(
+ N_("Command \"%s\" not allowed in included file"),
+ keyword);
+ else
+#endif
+ set = 1;
+ } else if (strcasecmp(keyword, "unset") == 0) {
+#ifdef ENABLE_COLOR
+ if (syntax_only)
+ rcfile_error(
+ N_("Command \"%s\" not allowed in included file"),
+ keyword);
+ else
+#endif
+ set = -1;
+ }
+#ifdef ENABLE_COLOR
+ else if (strcasecmp(keyword, "include") == 0) {
+ if (syntax_only)
+ rcfile_error(
+ N_("Command \"%s\" not allowed in included file"),
+ keyword);
+ else
+ parse_include(ptr);
+ } else if (strcasecmp(keyword, "syntax") == 0) {
+ if (endsyntax != NULL && endcolor == NULL)
+ rcfile_error(N_("Syntax \"%s\" has no color commands"),
+ endsyntax->desc);
+ parse_syntax(ptr);
+ }
+ else if (strcasecmp(keyword, "magic") == 0) {
+ parse_magictype(ptr);
+ } else if (strcasecmp(keyword, "header") == 0)
+ parse_headers(ptr);
+ else if (strcasecmp(keyword, "color") == 0)
+ parse_colors(ptr, FALSE);
+ else if (strcasecmp(keyword, "icolor") == 0)
+ parse_colors(ptr, TRUE);
+ else if (strcasecmp(keyword, "bind") == 0)
+ parse_keybinding(ptr);
+ else if (strcasecmp(keyword, "unbind") == 0)
+ parse_unbinding(ptr);
+#endif /* ENABLE_COLOR */
+ else
+ rcfile_error(N_("Command \"%s\" not understood"), keyword);
+
+ if (set == 0)
+ continue;
+
+ if (*ptr == '\0') {
+ rcfile_error(N_("Missing flag"));
+ continue;
+ }
+
+ option = ptr;
+ ptr = parse_next_word(ptr);
+
+ for (i = 0; rcopts[i].name != NULL; i++) {
+ if (strcasecmp(option, rcopts[i].name) == 0) {
+#ifdef DEBUG
+ fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name);
+#endif
+ if (set == 1) {
+ if (rcopts[i].flag != 0)
+ /* This option has a flag, so it doesn't take an
+ * argument. */
+ SET(rcopts[i].flag);
+ else {
+ /* This option doesn't have a flag, so it takes
+ * an argument. */
+ if (*ptr == '\0') {
+ rcfile_error(
+ N_("Option \"%s\" requires an argument"),
+ rcopts[i].name);
+ break;
+ }
+ option = ptr;
+ if (*option == '"')
+ option++;
+ ptr = parse_argument(ptr);
+
+ option = mallocstrcpy(NULL, option);
+#ifdef DEBUG
+ fprintf(stderr, "option = \"%s\"\n", option);
+#endif
+
+ /* Make sure option is a valid multibyte
+ * string. */
+ if (!is_valid_mbstring(option)) {
+ rcfile_error(
+ N_("Option is not a valid multibyte string"));
+ break;
+ }
+
+#ifndef DISABLE_OPERATINGDIR
+ if (strcasecmp(rcopts[i].name, "operatingdir") == 0)
+ operating_dir = option;
+ else
+#endif
+#ifndef DISABLE_WRAPJUSTIFY
+ if (strcasecmp(rcopts[i].name, "fill") == 0) {
+ if (!parse_num(option, &wrap_at)) {
+ rcfile_error(
+ N_("Requested fill size \"%s\" is invalid"),
+ option);
+ wrap_at = -CHARS_FROM_EOL;
+ } else
+ free(option);
+ } else
+#endif
+#ifndef NANO_TINY
+ if (strcasecmp(rcopts[i].name,
+ "matchbrackets") == 0) {
+ matchbrackets = option;
+ if (has_blank_mbchars(matchbrackets)) {
+ rcfile_error(
+ N_("Non-blank characters required"));
+ free(matchbrackets);
+ matchbrackets = NULL;
+ }
+ } else if (strcasecmp(rcopts[i].name,
+ "whitespace") == 0) {
+ whitespace = option;
+ if (mbstrlen(whitespace) != 2 ||
+ strlenpt(whitespace) != 2) {
+ rcfile_error(
+ N_("Two single-column characters required"));
+ free(whitespace);
+ whitespace = NULL;
+ } else {
+ whitespace_len[0] =
+ parse_mbchar(whitespace, NULL,
+ NULL);
+ whitespace_len[1] =
+ parse_mbchar(whitespace +
+ whitespace_len[0], NULL, NULL);
+ }
+ } else
+#endif
+#ifndef DISABLE_JUSTIFY
+ if (strcasecmp(rcopts[i].name, "punct") == 0) {
+ punct = option;
+ if (has_blank_mbchars(punct)) {
+ rcfile_error(
+ N_("Non-blank characters required"));
+ free(punct);
+ punct = NULL;
+ }
+ } else if (strcasecmp(rcopts[i].name,
+ "brackets") == 0) {
+ brackets = option;
+ if (has_blank_mbchars(brackets)) {
+ rcfile_error(
+ N_("Non-blank characters required"));
+ free(brackets);
+ brackets = NULL;
+ }
+ } else if (strcasecmp(rcopts[i].name,
+ "quotestr") == 0)
+ quotestr = option;
+ else
+#endif
+#ifndef NANO_TINY
+ if (strcasecmp(rcopts[i].name,
+ "backupdir") == 0)
+ backup_dir = option;
+ else
+#endif
+#ifndef DISABLE_SPELLER
+ if (strcasecmp(rcopts[i].name, "speller") == 0)
+ alt_speller = option;
+ else
+#endif
+ if (strcasecmp(rcopts[i].name,
+ "tabsize") == 0) {
+ if (!parse_num(option, &tabsize) ||
+ tabsize <= 0) {
+ rcfile_error(
+ N_("Requested tab size \"%s\" is invalid"),
+ option);
+ tabsize = -1;
+ } else
+ free(option);
+ } else
+ assert(FALSE);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "flag = %ld\n", rcopts[i].flag);
+#endif
+ } else if (rcopts[i].flag != 0)
+ UNSET(rcopts[i].flag);
+ else
+ rcfile_error(N_("Cannot unset flag \"%s\""),
+ rcopts[i].name);
+ /* Looks like we still need this specific hack for undo */
+ if (strcasecmp(rcopts[i].name, "undo") == 0)
+ shortcut_init(0);
+ break;
+ }
+ }
+ if (rcopts[i].name == NULL)
+ rcfile_error(N_("Unknown flag \"%s\""), option);
+ }
+
+#ifdef ENABLE_COLOR
+ if (endsyntax != NULL && endcolor == NULL)
+ rcfile_error(N_("Syntax \"%s\" has no color commands"),
+ endsyntax->desc);
+#endif
+
+ free(buf);
+ fclose(rcstream);
+ lineno = 0;
+
+ check_vitals_mapped();
+ return;
+}
+
+/* The main rcfile function. It tries to open the system-wide rcfile,
+ * followed by the current user's rcfile. */
+void do_rcfile(void)
+{
+ struct stat rcinfo;
+ FILE *rcstream;
+
+ nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc");
+
+ /* Don't open directories, character files, or block files. */
+ if (stat(nanorc, &rcinfo) != -1) {
+ if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
+ S_ISBLK(rcinfo.st_mode))
+ rcfile_error(S_ISDIR(rcinfo.st_mode) ?
+ _("\"%s\" is a directory") :
+ _("\"%s\" is a device file"), nanorc);
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Parsing file \"%s\"\n", nanorc);
+#endif
+
+ /* Try to open the system-wide nanorc. */
+ rcstream = fopen(nanorc, "rb");
+ if (rcstream != NULL)
+ parse_rcfile(rcstream
+#ifdef ENABLE_COLOR
+ , FALSE
+#endif
+ );
+
+#ifdef DISABLE_ROOTWRAPPING
+ /* We've already read SYSCONFDIR/nanorc, if it's there. If we're
+ * root, and --disable-wrapping-as-root is used, turn wrapping off
+ * now. */
+ if (geteuid() == NANO_ROOT_UID)
+ SET(NO_WRAP);
+#endif
+
+ get_homedir();
+
+ if (homedir == NULL)
+ rcfile_error(N_("I can't find my home directory! Wah!"));
+ else {
+#ifndef RCFILE_NAME
+#define RCFILE_NAME ".nanorc"
+#endif
+ nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2);
+ sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME);
+
+ /* Don't open directories, character files, or block files. */
+ if (stat(nanorc, &rcinfo) != -1) {
+ if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
+ S_ISBLK(rcinfo.st_mode))
+ rcfile_error(S_ISDIR(rcinfo.st_mode) ?
+ _("\"%s\" is a directory") :
+ _("\"%s\" is a device file"), nanorc);
+ }
+
+ /* Try to open the current user's nanorc. */
+ rcstream = fopen(nanorc, "rb");
+ if (rcstream == NULL) {
+ /* Don't complain about the file's not existing. */
+ if (errno != ENOENT)
+ rcfile_error(N_("Error reading %s: %s"), nanorc,
+ strerror(errno));
+ } else
+ parse_rcfile(rcstream
+#ifdef ENABLE_COLOR
+ , FALSE
+#endif
+ );
+ }
+
+ free(nanorc);
+ nanorc = NULL;
+
+ if (errors && !ISSET(QUIET)) {
+ errors = FALSE;
+ fprintf(stderr,
+ _("\nPress Enter to continue starting nano.\n"));
+ while (getchar() != '\n')
+ ;
+ }
+
+#ifdef ENABLE_COLOR
+ set_colorpairs();
+#endif
+}
+
+#endif /* ENABLE_NANORC */
diff --git a/src/search.c b/src/search.c
new file mode 100644
index 0000000..ca93098
--- /dev/null
+++ b/src/search.c
@@ -0,0 +1,1498 @@
+/* $Id: search.c 4527 2011-02-07 14:45:56Z astyanax $ */
+/**************************************************************************
+ * search.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+
+static bool search_last_line = FALSE;
+ /* Have we gone past the last line while searching? */
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+static bool history_changed = FALSE;
+ /* Have any of the history lists changed? */
+#endif
+#ifdef HAVE_REGEX_H
+static bool regexp_compiled = FALSE;
+ /* Have we compiled any regular expressions? */
+
+/* Compile the regular expression regexp to see if it's valid. Return
+ * TRUE if it is, or FALSE otherwise. */
+bool regexp_init(const char *regexp)
+{
+ int rc;
+
+ assert(!regexp_compiled);
+
+ rc = regcomp(&search_regexp, regexp, REG_EXTENDED
+#ifndef NANO_TINY
+ | (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE)
+#endif
+ );
+
+ if (rc != 0) {
+ size_t len = regerror(rc, &search_regexp, NULL, 0);
+ char *str = charalloc(len);
+
+ regerror(rc, &search_regexp, str, len);
+ statusbar(_("Bad regex \"%s\": %s"), regexp, str);
+ free(str);
+
+ return FALSE;
+ }
+
+ regexp_compiled = TRUE;
+
+ return TRUE;
+}
+
+/* Decompile the compiled regular expression we used in the last
+ * search, if any. */
+void regexp_cleanup(void)
+{
+ if (regexp_compiled) {
+ regexp_compiled = FALSE;
+ regfree(&search_regexp);
+ }
+}
+#endif
+
+/* Indicate on the statusbar that the string at str was not found by the
+ * last search. */
+void not_found_msg(const char *str)
+{
+ char *disp;
+ int numchars;
+
+ assert(str != NULL);
+
+ disp = display_string(str, 0, (COLS / 2) + 1, FALSE);
+ numchars = actual_x(disp, mbstrnlen(disp, COLS / 2));
+
+ statusbar(_("\"%.*s%s\" not found"), numchars, disp,
+ (disp[numchars] == '\0') ? "" : "...");
+
+ free(disp);
+}
+
+/* Abort the current search or replace. Clean up by displaying the main
+ * shortcut list, updating the screen if the mark was on before, and
+ * decompiling the compiled regular expression we used in the last
+ * search, if any. */
+void search_replace_abort(void)
+{
+ display_main_list();
+#ifndef NANO_TINY
+ if (openfile->mark_set)
+ edit_refresh();
+#endif
+#ifdef HAVE_REGEX_H
+ regexp_cleanup();
+#endif
+}
+
+/* Initialize the global search and replace strings. */
+void search_init_globals(void)
+{
+ if (last_search == NULL)
+ last_search = mallocstrcpy(NULL, "");
+ if (last_replace == NULL)
+ last_replace = mallocstrcpy(NULL, "");
+}
+
+/* Set up the system variables for a search or replace. If use_answer
+ * is TRUE, only set backupstring to answer. Return -2 to run the
+ * opposite program (search -> replace, replace -> search), return -1 if
+ * the search should be canceled (due to Cancel, a blank search string,
+ * Go to Line, or a failed regcomp()), return 0 on success, and return 1
+ * on rerun calling program.
+ *
+ * replacing is TRUE if we call from do_replace(), and FALSE if called
+ * from do_search(). */
+int search_init(bool replacing, bool use_answer)
+{
+ int i = 0;
+ char *buf;
+ sc *s;
+ void (*func)(void);
+ bool meta_key = FALSE, func_key = FALSE;
+ static char *backupstring = NULL;
+ /* The search string we'll be using. */
+
+ /* If backupstring doesn't exist, initialize it to "". */
+ if (backupstring == NULL)
+ backupstring = mallocstrcpy(NULL, "");
+
+ /* If use_answer is TRUE, set backupstring to answer and get out. */
+ if (use_answer) {
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 0;
+ }
+
+ /* We display the search prompt below. If the user types a partial
+ * search string and then Replace or a toggle, we will return to
+ * do_search() or do_replace() and be called again. In that case,
+ * we should put the same search string back up. */
+
+ search_init_globals();
+
+ if (last_search[0] != '\0') {
+ char *disp = display_string(last_search, 0, COLS / 3, FALSE);
+
+ buf = charalloc(strlen(disp) + 7);
+ /* We use (COLS / 3) here because we need to see more on the
+ * line. */
+ sprintf(buf, " [%s%s]", disp,
+ (strlenpt(last_search) > COLS / 3) ? "..." : "");
+ free(disp);
+ } else
+ buf = mallocstrcpy(NULL, "");
+
+ /* This is now one simple call. It just does a lot. */
+ i = do_prompt(FALSE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ replacing ? MREPLACE : MWHEREIS, backupstring,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ &search_history,
+#endif
+ edit_refresh, "%s%s%s%s%s%s", _("Search"),
+#ifndef NANO_TINY
+ /* TRANSLATORS: This string is just a modifier for the search
+ * prompt; no grammar is implied. */
+ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
+#endif
+ "",
+#ifdef HAVE_REGEX_H
+ /* TRANSLATORS: This string is just a modifier for the search
+ * prompt; no grammar is implied. */
+ ISSET(USE_REGEXP) ? _(" [Regexp]") :
+#endif
+ "",
+#ifndef NANO_TINY
+ /* TRANSLATORS: This string is just a modifier for the search
+ * prompt; no grammar is implied. */
+ ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
+#endif
+ "", replacing ?
+#ifndef NANO_TINY
+ openfile->mark_set ? _(" (to replace) in selection") :
+#endif
+ _(" (to replace)") : "", buf);
+
+ fflush(stderr);
+
+ /* Release buf now that we don't need it anymore. */
+ free(buf);
+
+ free(backupstring);
+ backupstring = NULL;
+
+ /* Cancel any search, or just return with no previous search. */
+ if (i == -1 || (i < 0 && *last_search == '\0') || (!replacing &&
+ i == 0 && *answer == '\0')) {
+ statusbar(_("Cancelled"));
+ return -1;
+ } else {
+ for (s = sclist; s != NULL; s = s->next)
+ if ((s->menu & currmenu) && i == s->seq) {
+ func = s->scfunc;
+ break;
+ }
+
+ if (i == -2 || i == 0 ) {
+#ifdef HAVE_REGEX_H
+ /* Use last_search if answer is an empty string, or
+ * answer if it isn't. */
+ if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ?
+ last_search : answer))
+ return -1;
+#endif
+ ;
+#ifndef NANO_TINY
+ } else if (func == case_sens_void) {
+ TOGGLE(CASE_SENSITIVE);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+ } else if (func == backwards_void) {
+ TOGGLE(BACKWARDS_SEARCH);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+#endif
+#ifdef HAVE_REGEX_H
+ } else if (func == regexp_void) {
+ TOGGLE(USE_REGEXP);
+ backupstring = mallocstrcpy(backupstring, answer);
+ return 1;
+#endif
+ } else if (func == do_replace || func == no_replace_void) {
+ backupstring = mallocstrcpy(backupstring, answer);
+ return -2; /* Call the opposite search function. */
+ } else if (func == do_gotolinecolumn_void) {
+ do_gotolinecolumn(openfile->current->lineno,
+ openfile->placewewant + 1, TRUE, TRUE, FALSE,
+ TRUE);
+ /* Put answer up on the statusbar and
+ * fall through. */
+ return 3;
+ } else {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Look for needle, starting at (current, current_x). If no_sameline is
+ * TRUE, skip over begin when looking for needle. begin is the line
+ * where we first started searching, at column begin_x. The return
+ * value specifies whether we found anything. If we did, set needle_len
+ * to the length of the string we found if it isn't NULL. */
+bool findnextstr(
+#ifndef DISABLE_SPELLER
+ bool whole_word,
+#endif
+ bool no_sameline, const filestruct *begin, size_t begin_x, const
+ char *needle, size_t *needle_len)
+{
+ size_t found_len;
+ /* The length of the match we find. */
+ size_t current_x_find = 0;
+ /* The location in the current line of the match we find. */
+ ssize_t current_y_find = openfile->current_y;
+ filestruct *fileptr = openfile->current;
+ const char *rev_start = fileptr->data, *found = NULL;
+ const subnfunc *f;
+ time_t lastkbcheck = time(NULL);
+
+ /* rev_start might end up 1 character before the start or after the
+ * end of the line. This won't be a problem because strstrwrapper()
+ * will return immediately and say that no match was found, and
+ * rev_start will be properly set when the search continues on the
+ * previous or next line. */
+ rev_start +=
+#ifndef NANO_TINY
+ ISSET(BACKWARDS_SEARCH) ?
+ openfile->current_x - 1 :
+#endif
+ openfile->current_x + 1;
+
+ /* Look for needle in the current line we're searching. */
+ enable_nodelay();
+ while (TRUE) {
+ if (time(NULL) - lastkbcheck > 1) {
+ lastkbcheck = time(NULL);
+ f = getfuncfromkey(edit);
+ if (f && f->scfunc == do_cancel) {
+ statusbar(_("Cancelled"));
+ return FALSE;
+ }
+ }
+
+ found = strstrwrapper(fileptr->data, needle, rev_start);
+
+ /* We've found a potential match. */
+ if (found != NULL) {
+#ifndef DISABLE_SPELLER
+ bool found_whole = FALSE;
+ /* Is this potential match a whole word? */
+#endif
+
+ /* Set found_len to the length of the potential match. */
+ found_len =
+#ifdef HAVE_REGEX_H
+ ISSET(USE_REGEXP) ?
+ regmatches[0].rm_eo - regmatches[0].rm_so :
+#endif
+ strlen(needle);
+
+#ifndef DISABLE_SPELLER
+ /* If we're searching for whole words, see if this potential
+ * match is a whole word. */
+ if (whole_word) {
+ char *word = mallocstrncpy(NULL, found, found_len + 1);
+ word[found_len] = '\0';
+
+ found_whole = is_whole_word(found - fileptr->data,
+ fileptr->data, word);
+ free(word);
+ }
+#endif
+
+ /* If we're searching for whole words and this potential
+ * match isn't a whole word, or if we're not allowed to find
+ * a match on the same line we started on and this potential
+ * match is on that line, continue searching. */
+ if (
+#ifndef DISABLE_SPELLER
+ (!whole_word || found_whole) &&
+#endif
+ (!no_sameline || fileptr != openfile->current))
+ break;
+ }
+
+ /* We've finished processing the file, so get out. */
+ if (search_last_line) {
+ not_found_msg(needle);
+ disable_nodelay();
+ return FALSE;
+ }
+
+ /* Move to the previous or next line in the file. */
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH)) {
+ fileptr = fileptr->prev;
+ current_y_find--;
+ } else {
+#endif
+ fileptr = fileptr->next;
+ current_y_find++;
+#ifndef NANO_TINY
+ }
+#endif
+
+ /* We've reached the start or end of the buffer, so wrap
+ * around. */
+ if (fileptr == NULL) {
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH)) {
+ fileptr = openfile->filebot;
+ current_y_find = editwinrows - 1;
+ } else {
+#endif
+ fileptr = openfile->fileage;
+ current_y_find = 0;
+#ifndef NANO_TINY
+ }
+#endif
+ statusbar(_("Search Wrapped"));
+ }
+
+ /* We've reached the original starting line. */
+ if (fileptr == begin)
+ search_last_line = TRUE;
+
+ rev_start = fileptr->data;
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH))
+ rev_start += strlen(fileptr->data);
+#endif
+ }
+
+ /* We found an instance. */
+ current_x_find = found - fileptr->data;
+
+ /* Ensure we haven't wrapped around again! */
+ if (search_last_line &&
+#ifndef NANO_TINY
+ ((!ISSET(BACKWARDS_SEARCH) && current_x_find > begin_x) ||
+ (ISSET(BACKWARDS_SEARCH) && current_x_find < begin_x))
+#else
+ current_x_find > begin_x
+#endif
+ ) {
+ not_found_msg(needle);
+ disable_nodelay();
+ return FALSE;
+ }
+
+ disable_nodelay();
+ /* We've definitely found something. */
+ openfile->current = fileptr;
+ openfile->current_x = current_x_find;
+ openfile->placewewant = xplustabs();
+ openfile->current_y = current_y_find;
+
+ /* needle_len holds the length of needle. */
+ if (needle_len != NULL)
+ *needle_len = found_len;
+
+ return TRUE;
+}
+
+/* Clear the flag indicating that a search reached the last line of the
+ * file. We need to do this just before a new search. */
+void findnextstr_wrap_reset(void)
+{
+ search_last_line = FALSE;
+}
+
+/* Search for a string. */
+void do_search(void)
+{
+ filestruct *fileptr = openfile->current;
+ size_t fileptr_x = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ int i;
+ bool didfind;
+
+ i = search_init(FALSE, FALSE);
+
+ if (i == -1)
+ /* Cancel, Go to Line, blank search string, or regcomp()
+ * failed. */
+ search_replace_abort();
+ else if (i == -2)
+ /* Replace. */
+ do_replace();
+#if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
+ else if (i == 1)
+ /* Case Sensitive, Backwards, or Regexp search toggle. */
+ do_search();
+#endif
+
+ if (i != 0)
+ return;
+
+ /* If answer is now "", copy last_search into answer. */
+ if (*answer == '\0')
+ answer = mallocstrcpy(answer, last_search);
+ else
+ last_search = mallocstrcpy(last_search, answer);
+
+#ifndef NANO_TINY
+ /* If answer is not "", add this search string to the search history
+ * list. */
+ if (answer[0] != '\0')
+ update_history(&search_history, answer);
+#endif
+
+ findnextstr_wrap_reset();
+ didfind = findnextstr(
+#ifndef DISABLE_SPELLER
+ FALSE,
+#endif
+ FALSE, openfile->current, openfile->current_x, answer, NULL);
+
+ /* Check to see if there's only one occurrence of the string and
+ * we're on it now. */
+ if (fileptr == openfile->current && fileptr_x ==
+ openfile->current_x && didfind) {
+#ifdef HAVE_REGEX_H
+ /* Do the search again, skipping over the current line, if we're
+ * doing a bol and/or eol regex search ("^", "$", or "^$"), so
+ * that we find one only once per line. We should only end up
+ * back at the same position if the string isn't found again, in
+ * which case it's the only occurrence. */
+ if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
+ last_search)) {
+ didfind = findnextstr(
+#ifndef DISABLE_SPELLER
+ FALSE,
+#endif
+ TRUE, openfile->current,
+ openfile->current_x, answer, NULL);
+ if (fileptr == openfile->current && fileptr_x ==
+ openfile->current_x && !didfind)
+ statusbar(_("This is the only occurrence"));
+ } else {
+#endif
+ statusbar(_("This is the only occurrence"));
+#ifdef HAVE_REGEX_H
+ }
+#endif
+ }
+
+ openfile->placewewant = xplustabs();
+ edit_redraw(fileptr, pww_save);
+ search_replace_abort();
+}
+
+#ifndef NANO_TINY
+/* Search for the last string without prompting. */
+void do_research(void)
+{
+ filestruct *fileptr = openfile->current;
+ size_t fileptr_x = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ bool didfind;
+
+ search_init_globals();
+
+ if (last_search[0] != '\0') {
+#ifdef HAVE_REGEX_H
+ /* Since answer is "", use last_search! */
+ if (ISSET(USE_REGEXP) && !regexp_init(last_search))
+ return;
+#endif
+
+ findnextstr_wrap_reset();
+ didfind = findnextstr(
+#ifndef DISABLE_SPELLER
+ FALSE,
+#endif
+ FALSE, openfile->current, openfile->current_x,
+ last_search, NULL);
+
+ /* Check to see if there's only one occurrence of the string and
+ * we're on it now. */
+ if (fileptr == openfile->current && fileptr_x ==
+ openfile->current_x && didfind) {
+#ifdef HAVE_REGEX_H
+ /* Do the search again, skipping over the current line, if
+ * we're doing a bol and/or eol regex search ("^", "$", or
+ * "^$"), so that we find one only once per line. We should
+ * only end up back at the same position if the string isn't
+ * found again, in which case it's the only occurrence. */
+ if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
+ last_search)) {
+ didfind = findnextstr(
+#ifndef DISABLE_SPELLER
+ FALSE,
+#endif
+ TRUE, openfile->current, openfile->current_x,
+ answer, NULL);
+ if (fileptr == openfile->current && fileptr_x ==
+ openfile->current_x && !didfind)
+ statusbar(_("This is the only occurrence"));
+ } else {
+#endif
+ statusbar(_("This is the only occurrence"));
+#ifdef HAVE_REGEX_H
+ }
+#endif
+ }
+ } else
+ statusbar(_("No current search pattern"));
+
+ openfile->placewewant = xplustabs();
+ edit_redraw(fileptr, pww_save);
+ search_replace_abort();
+}
+#endif
+
+#ifdef HAVE_REGEX_H
+int replace_regexp(char *string, bool create)
+{
+ /* We have a split personality here. If create is FALSE, just
+ * calculate the size of the replacement line (necessary because of
+ * subexpressions \1 to \9 in the replaced text). */
+
+ const char *c = last_replace;
+ size_t search_match_count = regmatches[0].rm_eo -
+ regmatches[0].rm_so;
+ size_t new_line_size = strlen(openfile->current->data) + 1 -
+ search_match_count;
+
+ /* Iterate through the replacement text to handle subexpression
+ * replacement using \1, \2, \3, etc. */
+ while (*c != '\0') {
+ int num = (*(c + 1) - '0');
+
+ if (*c != '\\' || num < 1 || num > 9 || num >
+ search_regexp.re_nsub) {
+ if (create)
+ *string++ = *c;
+ c++;
+ new_line_size++;
+ } else {
+ size_t i = regmatches[num].rm_eo - regmatches[num].rm_so;
+
+ /* Skip over the replacement expression. */
+ c += 2;
+
+ /* But add the length of the subexpression to new_size. */
+ new_line_size += i;
+
+ /* And if create is TRUE, append the result of the
+ * subexpression match to the new line. */
+ if (create) {
+ strncpy(string, openfile->current->data +
+ openfile->current_x + regmatches[num].rm_so, i);
+ string += i;
+ }
+ }
+ }
+
+ if (create)
+ *string = '\0';
+
+ return new_line_size;
+}
+#endif
+
+char *replace_line(const char *needle)
+{
+ char *copy;
+ size_t new_line_size, search_match_count;
+
+ /* Calculate the size of the new line. */
+#ifdef HAVE_REGEX_H
+ if (ISSET(USE_REGEXP)) {
+ search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
+ new_line_size = replace_regexp(NULL, FALSE);
+ } else {
+#endif
+ search_match_count = strlen(needle);
+ new_line_size = strlen(openfile->current->data) -
+ search_match_count + strlen(answer) + 1;
+#ifdef HAVE_REGEX_H
+ }
+#endif
+
+ /* Create the buffer. */
+ copy = charalloc(new_line_size);
+
+ /* The head of the original line. */
+ strncpy(copy, openfile->current->data, openfile->current_x);
+
+ /* The replacement text. */
+#ifdef HAVE_REGEX_H
+ if (ISSET(USE_REGEXP))
+ replace_regexp(copy + openfile->current_x, TRUE);
+ else
+#endif
+ strcpy(copy + openfile->current_x, answer);
+
+ /* The tail of the original line. */
+ assert(openfile->current_x + search_match_count <= strlen(openfile->current->data));
+
+ strcat(copy, openfile->current->data + openfile->current_x +
+ search_match_count);
+
+ return copy;
+}
+
+/* Step through each replace word and prompt user before replacing.
+ * Parameters real_current and real_current_x are needed in order to
+ * allow the cursor position to be updated when a word before the cursor
+ * is replaced by a shorter word.
+ *
+ * needle is the string to seek. We replace it with answer. Return -1
+ * if needle isn't found, else the number of replacements performed. If
+ * canceled isn't NULL, set it to TRUE if we canceled. */
+ssize_t do_replace_loop(
+#ifndef DISABLE_SPELLER
+ bool whole_word,
+#endif
+ bool *canceled, const filestruct *real_current, size_t
+ *real_current_x, const char *needle)
+{
+ ssize_t numreplaced = -1;
+ size_t match_len;
+ bool replaceall = FALSE;
+#ifdef HAVE_REGEX_H
+ /* The starting-line match and bol/eol regex flags. */
+ bool begin_line = FALSE, bol_or_eol = FALSE;
+#endif
+#ifndef NANO_TINY
+ bool old_mark_set = openfile->mark_set;
+ filestruct *edittop_save = openfile->edittop, *top, *bot;
+ size_t top_x, bot_x;
+ bool right_side_up = FALSE;
+ /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
+ * FALSE if (current, current_x) is. */
+
+ if (old_mark_set) {
+ /* If the mark is on, partition the filestruct so that it
+ * contains only the marked text, set edittop to the top of the
+ * partition, turn the mark off, and refresh the screen. */
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, &right_side_up);
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+ openfile->edittop = openfile->fileage;
+ openfile->mark_set = FALSE;
+#ifdef ENABLE_COLOR
+ reset_multis(openfile->current, TRUE);
+#endif
+ edit_refresh();
+ }
+#endif
+
+ if (canceled != NULL)
+ *canceled = FALSE;
+
+ findnextstr_wrap_reset();
+ while (findnextstr(
+#ifndef DISABLE_SPELLER
+ whole_word,
+#endif
+#ifdef HAVE_REGEX_H
+ /* We should find a bol and/or eol regex only once per line. If
+ * the bol_or_eol flag is set, it means that the last search
+ * found one on the beginning line, so we should skip over the
+ * beginning line when doing this search. */
+ bol_or_eol
+#else
+ FALSE
+#endif
+ , real_current, *real_current_x, needle, &match_len)) {
+ int i = 0;
+
+#ifdef HAVE_REGEX_H
+ /* If the bol_or_eol flag is set, we've found a match on the
+ * beginning line already, and we're still on the beginning line
+ * after the search, it means that we've wrapped around, so
+ * we're done. */
+ if (bol_or_eol && begin_line && openfile->current ==
+ real_current)
+ break;
+ /* Otherwise, set the begin_line flag if we've found a match on
+ * the beginning line, reset the bol_or_eol flag, and
+ * continue. */
+ else {
+ if (openfile->current == real_current)
+ begin_line = TRUE;
+ bol_or_eol = FALSE;
+ }
+#endif
+
+ if (!replaceall)
+ edit_refresh();
+
+ /* Indicate that we found the search string. */
+ if (numreplaced == -1)
+ numreplaced = 0;
+
+ if (!replaceall) {
+ size_t xpt = xplustabs();
+ char *exp_word = display_string(openfile->current->data,
+ xpt, strnlenpt(openfile->current->data,
+ openfile->current_x + match_len) - xpt, FALSE);
+
+ curs_set(0);
+
+ do_replace_highlight(TRUE, exp_word);
+
+ i = do_yesno_prompt(TRUE, _("Replace this instance?"));
+
+ do_replace_highlight(FALSE, exp_word);
+
+ free(exp_word);
+
+ curs_set(1);
+
+ if (i == -1) { /* We canceled the replace. */
+ if (canceled != NULL)
+ *canceled = TRUE;
+ break;
+ }
+ }
+
+#ifdef HAVE_REGEX_H
+ /* Set the bol_or_eol flag if we're doing a bol and/or eol regex
+ * replace ("^", "$", or "^$"). */
+ if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
+ needle))
+ bol_or_eol = TRUE;
+#endif
+
+ if (i > 0 || replaceall) { /* Yes, replace it!!!! */
+ char *copy;
+ size_t length_change;
+
+#ifndef NANO_TINY
+ update_undo(REPLACE);
+#endif
+ if (i == 2)
+ replaceall = TRUE;
+
+ copy = replace_line(needle);
+
+ length_change = strlen(copy) -
+ strlen(openfile->current->data);
+
+#ifndef NANO_TINY
+ /* If the mark was on and (mark_begin, mark_begin_x) was the
+ * top of it, don't change mark_begin_x. */
+ if (!old_mark_set || !right_side_up) {
+ /* Keep mark_begin_x in sync with the text changes. */
+ if (openfile->current == openfile->mark_begin &&
+ openfile->mark_begin_x > openfile->current_x) {
+ if (openfile->mark_begin_x < openfile->current_x +
+ match_len)
+ openfile->mark_begin_x = openfile->current_x;
+ else
+ openfile->mark_begin_x += length_change;
+ }
+ }
+
+ /* If the mark was on and (current, current_x) was the top
+ * of it, don't change real_current_x. */
+ if (!old_mark_set || right_side_up) {
+#endif
+ /* Keep real_current_x in sync with the text changes. */
+ if (openfile->current == real_current &&
+ openfile->current_x <= *real_current_x) {
+ if (*real_current_x <
+ openfile->current_x + match_len)
+ *real_current_x = openfile->current_x +
+ match_len;
+ *real_current_x += length_change;
+ }
+#ifndef NANO_TINY
+ }
+#endif
+
+ /* Set the cursor at the last character of the replacement
+ * text, so searching will resume after the replacement
+ * text. Note that current_x might be set to (size_t)-1
+ * here. */
+#ifndef NANO_TINY
+ if (!ISSET(BACKWARDS_SEARCH))
+#endif
+ openfile->current_x += match_len + length_change - 1;
+
+ /* Cleanup. */
+ openfile->totsize += mbstrlen(copy) -
+ mbstrlen(openfile->current->data);
+ free(openfile->current->data);
+ openfile->current->data = copy;
+
+#ifdef ENABLE_COLOR
+ reset_multis(openfile->current, TRUE);
+#endif
+ edit_refresh();
+ if (!replaceall) {
+#ifdef ENABLE_COLOR
+ /* If color syntaxes are available and turned on, we
+ * need to call edit_refresh(). */
+ if (openfile->colorstrings != NULL &&
+ !ISSET(NO_COLOR_SYNTAX))
+ edit_refresh();
+ else
+#endif
+ update_line(openfile->current, openfile->current_x);
+ }
+
+ set_modified();
+ numreplaced++;
+ }
+ }
+
+#ifndef NANO_TINY
+ if (old_mark_set) {
+ /* If the mark was on, unpartition the filestruct so that it
+ * contains all the text again, set edittop back to what it was
+ * before, turn the mark back on, and refresh the screen. */
+ unpartition_filestruct(&filepart);
+ openfile->edittop = edittop_save;
+ openfile->mark_set = TRUE;
+ edit_refresh();
+ }
+#endif
+
+ /* If the NO_NEWLINES flag isn't set, and text has been added to the
+ * magicline, make a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
+ new_magicline();
+
+ return numreplaced;
+}
+
+/* Replace a string. */
+void do_replace(void)
+{
+ filestruct *edittop_save, *begin;
+ size_t begin_x, pww_save;
+ bool meta_key = FALSE, func_key = FALSE;
+ ssize_t numreplaced;
+ int i;
+
+ if (ISSET(VIEW_MODE)) {
+ print_view_warning();
+ search_replace_abort();
+ return;
+ }
+
+ i = search_init(TRUE, FALSE);
+ if (i == -1) {
+ /* Cancel, Go to Line, blank search string, or regcomp()
+ * failed. */
+ search_replace_abort();
+ return;
+ } else if (i == -2) {
+ /* No Replace. */
+ do_search();
+ return;
+ } else if (i == 1)
+ /* Case Sensitive, Backwards, or Regexp search toggle. */
+ do_replace();
+
+ if (i != 0)
+ return;
+
+ /* If answer is not "", add answer to the search history list and
+ * copy answer into last_search. */
+ if (answer[0] != '\0') {
+#ifndef NANO_TINY
+ update_history(&search_history, answer);
+#endif
+ last_search = mallocstrcpy(last_search, answer);
+ }
+
+ last_replace = mallocstrcpy(last_replace, "");
+
+ i = do_prompt(FALSE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ MREPLACE2, last_replace,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ &replace_history,
+#endif
+ edit_refresh, _("Replace with"));
+
+#ifndef NANO_TINY
+ /* Add this replace string to the replace history list. i == 0
+ * means that the string is not "". */
+ if (i == 0)
+ update_history(&replace_history, answer);
+#endif
+
+ if (i != 0 && i != -2) {
+ if (i == -1) { /* Cancel. */
+ if (last_replace[0] != '\0')
+ answer = mallocstrcpy(answer, last_replace);
+ statusbar(_("Cancelled"));
+ }
+ search_replace_abort();
+ return;
+ }
+
+ last_replace = mallocstrcpy(last_replace, answer);
+
+ /* Save where we are. */
+ edittop_save = openfile->edittop;
+ begin = openfile->current;
+ begin_x = openfile->current_x;
+ pww_save = openfile->placewewant;
+
+ numreplaced = do_replace_loop(
+#ifndef DISABLE_SPELLER
+ FALSE,
+#endif
+ NULL, begin, &begin_x, last_search);
+
+ /* Restore where we were. */
+ openfile->edittop = edittop_save;
+ openfile->current = begin;
+ openfile->current_x = begin_x;
+ openfile->placewewant = pww_save;
+
+ edit_refresh();
+
+ if (numreplaced >= 0)
+ statusbar(P_("Replaced %lu occurrence",
+ "Replaced %lu occurrences", (unsigned long)numreplaced),
+ (unsigned long)numreplaced);
+
+ search_replace_abort();
+}
+
+/* Go to the specified line and column, or ask for them if interactive
+ * is TRUE. Save the x-coordinate and y-coordinate if save_pos is TRUE.
+ * Update the screen afterwards if allow_update is TRUE. Note that both
+ * the line and column numbers should be one-based. */
+void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
+ bool interactive, bool save_pos, bool allow_update)
+{
+ bool meta_key = FALSE, func_key = FALSE;
+ const sc *s;
+
+ if (interactive) {
+ char *ans = mallocstrcpy(NULL, answer);
+
+ /* Ask for the line and column. */
+ int i = do_prompt(FALSE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ MGOTOLINE, use_answer ? ans : "",
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ NULL,
+#endif
+ edit_refresh, _("Enter line number, column number"));
+
+ free(ans);
+
+ /* Cancel, or Enter with blank string. */
+ if (i < 0) {
+ statusbar(_("Cancelled"));
+ display_main_list();
+ return;
+ }
+
+
+ s = get_shortcut(currmenu, &i, &meta_key, &func_key);
+ if (s && s->scfunc == gototext_void) {
+ /* Keep answer up on the statusbar. */
+ search_init(TRUE, TRUE);
+
+ do_search();
+ return;
+ }
+
+ /* Do a bounds check. Display a warning on an out-of-bounds
+ * line or column number only if we hit Enter at the statusbar
+ * prompt. */
+ if (!parse_line_column(answer, &line, &column) || line < 1 ||
+ column < 1) {
+ if (i == 0)
+ statusbar(_("Invalid line or column number"));
+ display_main_list();
+ return;
+ }
+ } else {
+ if (line < 1)
+ line = openfile->current->lineno;
+
+ if (column < 1)
+ column = openfile->placewewant + 1;
+ }
+
+ for (openfile->current = openfile->fileage;
+ openfile->current != openfile->filebot && line > 1; line--)
+ openfile->current = openfile->current->next;
+
+ openfile->current_x = actual_x(openfile->current->data, column - 1);
+ openfile->placewewant = column - 1;
+
+ /* Put the top line of the edit window in range of the current line.
+ * If save_pos is TRUE, don't change the cursor position when doing
+ * it. */
+ edit_update(save_pos ? NONE : CENTER);
+
+ /* If allow_update is TRUE, update the screen. */
+ if (allow_update)
+ edit_refresh();
+
+ display_main_list();
+}
+
+/* Go to the specified line and column, asking for them beforehand. */
+void do_gotolinecolumn_void(void)
+{
+ do_gotolinecolumn(openfile->current->lineno,
+ openfile->placewewant + 1, FALSE, TRUE, FALSE, TRUE);
+}
+
+#ifndef DISABLE_SPELLER
+/* Go to the line with the number specified in pos_line, the
+ * x-coordinate specified in pos_x, the y-coordinate specified in pos_y,
+ * and the place we want specified in pos_pww. */
+void do_gotopos(ssize_t pos_line, size_t pos_x, ssize_t pos_y, size_t
+ pos_pww)
+{
+ /* Since do_gotolinecolumn() resets the x-coordinate but not the
+ * y-coordinate, set the coordinates up this way. */
+ openfile->current_y = pos_y;
+ do_gotolinecolumn(pos_line, pos_x + 1, FALSE, FALSE, TRUE, TRUE);
+
+ /* Set the rest of the coordinates up. */
+ openfile->placewewant = pos_pww;
+ update_line(openfile->current, pos_x);
+}
+#endif
+
+#ifndef NANO_TINY
+/* Search for a match to one of the two characters in bracket_set. If
+ * reverse is TRUE, search backwards for the leftmost bracket.
+ * Otherwise, search forwards for the rightmost bracket. Return TRUE if
+ * we found a match, and FALSE otherwise. */
+bool find_bracket_match(bool reverse, const char *bracket_set)
+{
+ filestruct *fileptr = openfile->current;
+ const char *rev_start = NULL, *found = NULL;
+ ssize_t current_y_find = openfile->current_y;
+
+ assert(mbstrlen(bracket_set) == 2);
+
+ /* rev_start might end up 1 character before the start or after the
+ * end of the line. This won't be a problem because we'll skip over
+ * it below in that case, and rev_start will be properly set when
+ * the search continues on the previous or next line. */
+ rev_start = reverse ? fileptr->data + (openfile->current_x - 1) :
+ fileptr->data + (openfile->current_x + 1);
+
+ /* Look for either of the two characters in bracket_set. rev_start
+ * can be 1 character before the start or after the end of the line.
+ * In either case, just act as though no match is found. */
+ while (TRUE) {
+ found = ((rev_start > fileptr->data && *(rev_start - 1) ==
+ '\0') || rev_start < fileptr->data) ? NULL : (reverse ?
+ mbrevstrpbrk(fileptr->data, bracket_set, rev_start) :
+ mbstrpbrk(rev_start, bracket_set));
+
+ /* We've found a potential match. */
+ if (found != NULL)
+ break;
+
+ if (reverse) {
+ fileptr = fileptr->prev;
+ current_y_find--;
+ } else {
+ fileptr = fileptr->next;
+ current_y_find++;
+ }
+
+ /* We've reached the start or end of the buffer, so get out. */
+ if (fileptr == NULL)
+ return FALSE;
+
+ rev_start = fileptr->data;
+ if (reverse)
+ rev_start += strlen(fileptr->data);
+ }
+
+ /* We've definitely found something. */
+ openfile->current = fileptr;
+ openfile->current_x = found - fileptr->data;
+ openfile->placewewant = xplustabs();
+ openfile->current_y = current_y_find;
+
+ return TRUE;
+}
+
+/* Search for a match to the bracket at the current cursor position, if
+ * there is one. */
+void do_find_bracket(void)
+{
+ filestruct *current_save;
+ size_t current_x_save, pww_save;
+ const char *ch;
+ /* The location in matchbrackets of the bracket at the current
+ * cursor position. */
+ int ch_len;
+ /* The length of ch in bytes. */
+ const char *wanted_ch;
+ /* The location in matchbrackets of the bracket complementing
+ * the bracket at the current cursor position. */
+ int wanted_ch_len;
+ /* The length of wanted_ch in bytes. */
+ char *bracket_set;
+ /* The pair of characters in ch and wanted_ch. */
+ size_t i;
+ /* Generic loop variable. */
+ size_t matchhalf;
+ /* The number of single-byte characters in one half of
+ * matchbrackets. */
+ size_t mbmatchhalf;
+ /* The number of multibyte characters in one half of
+ * matchbrackets. */
+ size_t count = 1;
+ /* The initial bracket count. */
+ bool reverse;
+ /* The direction we search. */
+ char *found_ch;
+ /* The character we find. */
+
+ assert(mbstrlen(matchbrackets) % 2 == 0);
+
+ ch = openfile->current->data + openfile->current_x;
+
+ if (ch == '\0' || (ch = mbstrchr(matchbrackets, ch)) == NULL) {
+ statusbar(_("Not a bracket"));
+ return;
+ }
+
+ /* Save where we are. */
+ current_save = openfile->current;
+ current_x_save = openfile->current_x;
+ pww_save = openfile->placewewant;
+
+ /* If we're on an opening bracket, which must be in the first half
+ * of matchbrackets, we want to search forwards for a closing
+ * bracket. If we're on a closing bracket, which must be in the
+ * second half of matchbrackets, we want to search backwards for an
+ * opening bracket. */
+ matchhalf = 0;
+ mbmatchhalf = mbstrlen(matchbrackets) / 2;
+
+ for (i = 0; i < mbmatchhalf; i++)
+ matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL,
+ NULL);
+
+ reverse = ((ch - matchbrackets) >= matchhalf);
+
+ /* If we're on an opening bracket, set wanted_ch to the character
+ * that's matchhalf characters after ch. If we're on a closing
+ * bracket, set wanted_ch to the character that's matchhalf
+ * characters before ch. */
+ wanted_ch = ch;
+
+ while (mbmatchhalf > 0) {
+ if (reverse)
+ wanted_ch = matchbrackets + move_mbleft(matchbrackets,
+ wanted_ch - matchbrackets);
+ else
+ wanted_ch += move_mbright(wanted_ch, 0);
+
+ mbmatchhalf--;
+ }
+
+ ch_len = parse_mbchar(ch, NULL, NULL);
+ wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL);
+
+ /* Fill bracket_set in with the values of ch and wanted_ch. */
+ bracket_set = charalloc((mb_cur_max() * 2) + 1);
+ strncpy(bracket_set, ch, ch_len);
+ strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len);
+ null_at(&bracket_set, ch_len + wanted_ch_len);
+
+ found_ch = charalloc(mb_cur_max() + 1);
+
+ while (TRUE) {
+ if (find_bracket_match(reverse, bracket_set)) {
+ /* If we found an identical bracket, increment count. If we
+ * found a complementary bracket, decrement it. */
+ parse_mbchar(openfile->current->data + openfile->current_x,
+ found_ch, NULL);
+ count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1;
+
+ /* If count is zero, we've found a matching bracket. Update
+ * the screen and get out. */
+ if (count == 0) {
+ edit_redraw(current_save, pww_save);
+ break;
+ }
+ } else {
+ /* We didn't find either an opening or closing bracket.
+ * Indicate this, restore where we were, and get out. */
+ statusbar(_("No matching bracket"));
+ openfile->current = current_save;
+ openfile->current_x = current_x_save;
+ openfile->placewewant = pww_save;
+ break;
+ }
+ }
+
+ /* Clean up. */
+ free(bracket_set);
+ free(found_ch);
+}
+
+#ifdef ENABLE_NANORC
+/* Indicate whether any of the history lists have changed. */
+bool history_has_changed(void)
+{
+ return history_changed;
+}
+#endif
+
+/* Initialize the search and replace history lists. */
+void history_init(void)
+{
+ search_history = make_new_node(NULL);
+ search_history->data = mallocstrcpy(NULL, "");
+ searchage = search_history;
+ searchbot = search_history;
+
+ replace_history = make_new_node(NULL);
+ replace_history->data = mallocstrcpy(NULL, "");
+ replaceage = replace_history;
+ replacebot = replace_history;
+}
+
+/* Set the current position in the history list h to the bottom. */
+void history_reset(const filestruct *h)
+{
+ if (h == search_history)
+ search_history = searchbot;
+ else if (h == replace_history)
+ replace_history = replacebot;
+}
+
+/* Return the first node containing the first len characters of the
+ * string s in the history list, starting at h_start and ending at
+ * h_end, or NULL if there isn't one. */
+filestruct *find_history(const filestruct *h_start, const filestruct
+ *h_end, const char *s, size_t len)
+{
+ const filestruct *p;
+
+ for (p = h_start; p != h_end->next && p != NULL; p = p->next) {
+ if (strncmp(s, p->data, len) == 0)
+ return (filestruct *)p;
+ }
+
+ return NULL;
+}
+
+/* Update a history list. h should be the current position in the
+ * list. */
+void update_history(filestruct **h, const char *s)
+{
+ filestruct **hage = NULL, **hbot = NULL, *p;
+
+ assert(h != NULL && s != NULL);
+
+ if (*h == search_history) {
+ hage = &searchage;
+ hbot = &searchbot;
+ } else if (*h == replace_history) {
+ hage = &replaceage;
+ hbot = &replacebot;
+ }
+
+ assert(hage != NULL && hbot != NULL);
+
+ /* If this string is already in the history, delete it. */
+ p = find_history(*hage, *hbot, s, strlen(s));
+
+ if (p != NULL) {
+ filestruct *foo, *bar;
+
+ /* If the string is at the beginning, move the beginning down to
+ * the next string. */
+ if (p == *hage)
+ *hage = (*hage)->next;
+
+ /* Delete the string. */
+ foo = p;
+ bar = p->next;
+ unlink_node(foo);
+ delete_node(foo);
+ if (bar != NULL)
+ renumber(bar);
+ }
+
+ /* If the history is full, delete the beginning entry to make room
+ * for the new entry at the end. We assume that MAX_SEARCH_HISTORY
+ * is greater than zero. */
+ if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) {
+ filestruct *foo = *hage;
+
+ *hage = (*hage)->next;
+ unlink_node(foo);
+ delete_node(foo);
+ renumber(*hage);
+ }
+
+ /* Add the new entry to the end. */
+ (*hbot)->data = mallocstrcpy((*hbot)->data, s);
+ splice_node(*hbot, make_new_node(*hbot), (*hbot)->next);
+ *hbot = (*hbot)->next;
+ (*hbot)->data = mallocstrcpy(NULL, "");
+
+#ifdef ENABLE_NANORC
+ /* Indicate that the history's been changed. */
+ history_changed = TRUE;
+#endif
+
+ /* Set the current position in the list to the bottom. */
+ *h = *hbot;
+}
+
+/* Move h to the string in the history list just before it, and return
+ * that string. If there isn't one, don't move h and return NULL. */
+char *get_history_older(filestruct **h)
+{
+ assert(h != NULL);
+
+ if ((*h)->prev == NULL)
+ return NULL;
+
+ *h = (*h)->prev;
+
+ return (*h)->data;
+}
+
+/* Move h to the string in the history list just after it, and return
+ * that string. If there isn't one, don't move h and return NULL. */
+char *get_history_newer(filestruct **h)
+{
+ assert(h != NULL);
+
+ if ((*h)->next == NULL)
+ return NULL;
+
+ *h = (*h)->next;
+
+ return (*h)->data;
+}
+
+/* More placeholders */
+void get_history_newer_void(void)
+{
+ ;
+}
+void get_history_older_void(void)
+{
+ ;
+}
+
+#ifndef DISABLE_TABCOMP
+/* Move h to the next string that's a tab completion of the string s,
+ * looking at only the first len characters of s, and return that
+ * string. If there isn't one, or if len is 0, don't move h and return
+ * s. */
+char *get_history_completion(filestruct **h, const char *s, size_t len)
+{
+ assert(s != NULL);
+
+ if (len > 0) {
+ filestruct *hage = NULL, *hbot = NULL, *p;
+
+ assert(h != NULL);
+
+ if (*h == search_history) {
+ hage = searchage;
+ hbot = searchbot;
+ } else if (*h == replace_history) {
+ hage = replaceage;
+ hbot = replacebot;
+ }
+
+ assert(hage != NULL && hbot != NULL);
+
+ /* Search the history list from the current position to the
+ * bottom for a match of len characters. Skip over an exact
+ * match. */
+ p = find_history((*h)->next, hbot, s, len);
+
+ while (p != NULL && strcmp(p->data, s) == 0)
+ p = find_history(p->next, hbot, s, len);
+
+ if (p != NULL) {
+ *h = p;
+ return (*h)->data;
+ }
+
+ /* Search the history list from the top to the current position
+ * for a match of len characters. Skip over an exact match. */
+ p = find_history(hage, *h, s, len);
+
+ while (p != NULL && strcmp(p->data, s) == 0)
+ p = find_history(p->next, *h, s, len);
+
+ if (p != NULL) {
+ *h = p;
+ return (*h)->data;
+ }
+ }
+
+ /* If we're here, we didn't find a match, we didn't find an inexact
+ * match, or len is 0. Return s. */
+ return (char *)s;
+}
+#endif /* !DISABLE_TABCOMP */
+#endif /* !NANO_TINY */
diff --git a/src/text.c b/src/text.c
new file mode 100644
index 0000000..7abe712
--- /dev/null
+++ b/src/text.c
@@ -0,0 +1,3079 @@
+/* $Id: text.c 4544 2011-05-10 05:43:08Z astyanax $ */
+/**************************************************************************
+ * text.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#ifndef NANO_TINY
+static pid_t pid = -1;
+ /* The PID of the forked process in execute_command(), for use
+ * with the cancel_command() signal handler. */
+#endif
+#ifndef DISABLE_WRAPPING
+static bool prepend_wrap = FALSE;
+ /* Should we prepend wrapped text to the next line? */
+#endif
+#ifndef DISABLE_JUSTIFY
+static filestruct *jusbottom = NULL;
+ /* Pointer to the end of the justify buffer. */
+#endif
+
+#ifndef NANO_TINY
+/* Toggle the mark. */
+void do_mark(void)
+{
+ openfile->mark_set = !openfile->mark_set;
+ if (openfile->mark_set) {
+ statusbar(_("Mark Set"));
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x = openfile->current_x;
+ } else {
+ statusbar(_("Mark Unset"));
+ openfile->mark_begin = NULL;
+ openfile->mark_begin_x = 0;
+ edit_refresh();
+ }
+}
+#endif /* !NANO_TINY */
+
+/* Delete the character under the cursor. */
+void do_delete(void)
+{
+ size_t orig_lenpt = 0;
+
+#ifndef NANO_TINY
+ update_undo(DEL);
+#endif
+
+ assert(openfile->current != NULL && openfile->current->data != NULL && openfile->current_x <= strlen(openfile->current->data));
+
+ openfile->placewewant = xplustabs();
+
+ if (openfile->current->data[openfile->current_x] != '\0') {
+ int char_buf_len = parse_mbchar(openfile->current->data +
+ openfile->current_x, NULL, NULL);
+ size_t line_len = strlen(openfile->current->data +
+ openfile->current_x);
+
+ assert(openfile->current_x < strlen(openfile->current->data));
+
+ if (ISSET(SOFTWRAP))
+ orig_lenpt = strlenpt(openfile->current->data);
+
+ /* Let's get dangerous. */
+ charmove(&openfile->current->data[openfile->current_x],
+ &openfile->current->data[openfile->current_x +
+ char_buf_len], line_len - char_buf_len + 1);
+
+ null_at(&openfile->current->data, openfile->current_x +
+ line_len - char_buf_len);
+#ifndef NANO_TINY
+ if (openfile->mark_set && openfile->mark_begin ==
+ openfile->current && openfile->current_x <
+ openfile->mark_begin_x)
+ openfile->mark_begin_x -= char_buf_len;
+#endif
+ openfile->totsize--;
+ } else if (openfile->current != openfile->filebot) {
+ filestruct *foo = openfile->current->next;
+
+ assert(openfile->current_x == strlen(openfile->current->data));
+
+ /* If we're deleting at the end of a line, we need to call
+ * edit_refresh(). */
+ if (openfile->current->data[openfile->current_x] == '\0')
+ edit_refresh_needed = TRUE;
+
+ openfile->current->data = charealloc(openfile->current->data,
+ openfile->current_x + strlen(foo->data) + 1);
+ strcpy(openfile->current->data + openfile->current_x,
+ foo->data);
+#ifndef NANO_TINY
+ if (openfile->mark_set && openfile->mark_begin ==
+ openfile->current->next) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x += openfile->current_x;
+ }
+#endif
+ if (openfile->filebot == foo)
+ openfile->filebot = openfile->current;
+
+ unlink_node(foo);
+ delete_node(foo);
+ renumber(openfile->current);
+ openfile->totsize--;
+
+ /* If the NO_NEWLINES flag isn't set, and text has been added to
+ * the magicline as a result of deleting at the end of the line
+ * before filebot, add a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->current ==
+ openfile->filebot && openfile->current->data[0] != '\0')
+ new_magicline();
+ } else
+ return;
+
+ if (ISSET(SOFTWRAP) && edit_refresh_needed == FALSE)
+ if (strlenpt(openfile->current->data) / COLS != orig_lenpt / COLS)
+ edit_refresh_needed = TRUE;
+
+ set_modified();
+
+ if (edit_refresh_needed == FALSE)
+ update_line(openfile->current, openfile->current_x);
+}
+
+/* Backspace over one character. That is, move the cursor left one
+ * character, and then delete the character under the cursor. */
+void do_backspace(void)
+{
+ if (openfile->current != openfile->fileage ||
+ openfile->current_x > 0) {
+ do_left();
+ do_delete();
+ }
+}
+
+/* Insert a tab. If the TABS_TO_SPACES flag is set, insert the number
+ * of spaces that a tab would normally take up. */
+void do_tab(void)
+{
+#ifndef NANO_TINY
+ if (ISSET(TABS_TO_SPACES)) {
+ char *output;
+ size_t output_len = 0, new_pww = xplustabs();
+
+ do {
+ new_pww++;
+ output_len++;
+ } while (new_pww % tabsize != 0);
+
+ output = charalloc(output_len + 1);
+
+ charset(output, ' ', output_len);
+ output[output_len] = '\0';
+
+ do_output(output, output_len, TRUE);
+
+ free(output);
+ } else {
+#endif
+ do_output((char *) "\t", 1, TRUE);
+#ifndef NANO_TINY
+ }
+#endif
+}
+
+#ifndef NANO_TINY
+/* Indent or unindent the current line (or, if the mark is on, all lines
+ * covered by the mark) len columns, depending on whether len is
+ * positive or negative. If the TABS_TO_SPACES flag is set, indent or
+ * unindent by len spaces. Otherwise, indent or unindent by (len /
+ * tabsize) tabs and (len % tabsize) spaces. */
+void do_indent(ssize_t cols)
+{
+ bool indent_changed = FALSE;
+ /* Whether any indenting or unindenting was done. */
+ bool unindent = FALSE;
+ /* Whether we're unindenting text. */
+ char *line_indent = NULL;
+ /* The text added to each line in order to indent it. */
+ size_t line_indent_len = 0;
+ /* The length of the text added to each line in order to indent
+ * it. */
+ filestruct *top, *bot, *f;
+ size_t top_x, bot_x;
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+ /* If cols is zero, get out. */
+ if (cols == 0)
+ return;
+
+ /* If cols is negative, make it positive and set unindent to
+ * TRUE. */
+ if (cols < 0) {
+ cols = -cols;
+ unindent = TRUE;
+ /* Otherwise, we're indenting, in which case the file will always be
+ * modified, so set indent_changed to TRUE. */
+ } else
+ indent_changed = TRUE;
+
+ /* If the mark is on, use all lines covered by the mark. */
+ if (openfile->mark_set)
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, NULL);
+ /* Otherwise, use the current line. */
+ else {
+ top = openfile->current;
+ bot = top;
+ }
+
+ if (!unindent) {
+ /* Set up the text we'll be using as indentation. */
+ line_indent = charalloc(cols + 1);
+
+ if (ISSET(TABS_TO_SPACES)) {
+ /* Set the indentation to cols spaces. */
+ charset(line_indent, ' ', cols);
+ line_indent_len = cols;
+ } else {
+ /* Set the indentation to (cols / tabsize) tabs and (cols %
+ * tabsize) spaces. */
+ size_t num_tabs = cols / tabsize;
+ size_t num_spaces = cols % tabsize;
+
+ charset(line_indent, '\t', num_tabs);
+ charset(line_indent + num_tabs, ' ', num_spaces);
+
+ line_indent_len = num_tabs + num_spaces;
+ }
+
+ line_indent[line_indent_len] = '\0';
+ }
+
+ /* Go through each line of the text. */
+ for (f = top; f != bot->next; f = f->next) {
+ size_t line_len = strlen(f->data);
+ size_t indent_len = indent_length(f->data);
+
+ if (!unindent) {
+ /* If we're indenting, add the characters in line_indent to
+ * the beginning of the non-whitespace text of this line. */
+ f->data = charealloc(f->data, line_len +
+ line_indent_len + 1);
+ charmove(&f->data[indent_len + line_indent_len],
+ &f->data[indent_len], line_len - indent_len + 1);
+ strncpy(f->data + indent_len, line_indent, line_indent_len);
+ openfile->totsize += line_indent_len;
+
+ /* Keep track of the change in the current line. */
+ if (openfile->mark_set && f == openfile->mark_begin &&
+ openfile->mark_begin_x >= indent_len)
+ openfile->mark_begin_x += line_indent_len;
+
+ if (f == openfile->current && openfile->current_x >=
+ indent_len)
+ openfile->current_x += line_indent_len;
+
+ /* If the NO_NEWLINES flag isn't set, and this is the
+ * magicline, add a new magicline. */
+ if (!ISSET(NO_NEWLINES) && f == openfile->filebot)
+ new_magicline();
+ } else {
+ size_t indent_col = strnlenpt(f->data, indent_len);
+ /* The length in columns of the indentation on this
+ * line. */
+
+ if (cols <= indent_col) {
+ size_t indent_new = actual_x(f->data, indent_col -
+ cols);
+ /* The length of the indentation remaining on
+ * this line after we unindent. */
+ size_t indent_shift = indent_len - indent_new;
+ /* The change in the indentation on this line
+ * after we unindent. */
+
+ /* If we're unindenting, and there's at least cols
+ * columns' worth of indentation at the beginning of the
+ * non-whitespace text of this line, remove it. */
+ charmove(&f->data[indent_new], &f->data[indent_len],
+ line_len - indent_shift - indent_new + 1);
+ null_at(&f->data, line_len - indent_shift + 1);
+ openfile->totsize -= indent_shift;
+
+ /* Keep track of the change in the current line. */
+ if (openfile->mark_set && f == openfile->mark_begin &&
+ openfile->mark_begin_x > indent_new) {
+ if (openfile->mark_begin_x <= indent_len)
+ openfile->mark_begin_x = indent_new;
+ else
+ openfile->mark_begin_x -= indent_shift;
+ }
+
+ if (f == openfile->current && openfile->current_x >
+ indent_new) {
+ if (openfile->current_x <= indent_len)
+ openfile->current_x = indent_new;
+ else
+ openfile->current_x -= indent_shift;
+ }
+
+ /* We've unindented, so set indent_changed to TRUE. */
+ if (!indent_changed)
+ indent_changed = TRUE;
+ }
+ }
+ }
+
+ if (!unindent)
+ /* Clean up. */
+ free(line_indent);
+
+ if (indent_changed) {
+ /* Mark the file as modified. */
+ set_modified();
+
+ /* Update the screen. */
+ edit_refresh_needed = TRUE;
+ }
+}
+
+/* Indent the current line, or all lines covered by the mark if the mark
+ * is on, tabsize columns. */
+void do_indent_void(void)
+{
+ do_indent(tabsize);
+}
+
+/* Unindent the current line, or all lines covered by the mark if the
+ * mark is on, tabsize columns. */
+void do_unindent(void)
+{
+ do_indent(-tabsize);
+}
+
+/* undo a cut, or re-do an uncut */
+void undo_cut(undo *u)
+{
+ /* If we cut the magicline may was well not crash :/ */
+ if (!u->cutbuffer)
+ return;
+
+ cutbuffer = copy_filestruct(u->cutbuffer);
+
+ /* Compute cutbottom for the uncut using out copy */
+ for (cutbottom = cutbuffer; cutbottom->next != NULL; cutbottom = cutbottom->next)
+ ;
+
+ /* Get to where we need to uncut from */
+ if (u->mark_set && u->mark_begin_lineno < u->lineno)
+ do_gotolinecolumn(u->mark_begin_lineno, u->mark_begin_x+1, FALSE, FALSE, FALSE, FALSE);
+ else
+ do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE);
+
+ copy_from_filestruct(cutbuffer, cutbottom);
+ free_filestruct(cutbuffer);
+ cutbuffer = NULL;
+
+}
+
+/* Re-do a cut, or undo an uncut */
+void redo_cut(undo *u) {
+ int i;
+ filestruct *t, *c;
+
+ /* If we cut the magicline may was well not crash :/ */
+ if (!u->cutbuffer)
+ return;
+
+ do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE);
+ openfile->mark_set = u->mark_set;
+ if (cutbuffer)
+ free(cutbuffer);
+ cutbuffer = NULL;
+
+ /* Move ahead the same # lines we had if a marked cut */
+ if (u->mark_set) {
+ for (i = 1, t = openfile->fileage; i != u->mark_begin_lineno; i++)
+ t = t->next;
+ openfile->mark_begin = t;
+ } else if (!u->to_end) {
+ /* Here we have a regular old potentially multi-line ^K cut. We'll
+ need to trick nano into thinking it's a marked cut to cut more
+ than one line again */
+ for (c = u->cutbuffer, t = openfile->current; c->next != NULL && t->next != NULL; ) {
+
+#ifdef DEBUG
+ fprintf(stderr, "Advancing, lineno = %lu, data = \"%s\"\n", (unsigned long) t->lineno, t->data);
+#endif
+ c = c->next;
+ t = t->next;
+ }
+ openfile->mark_begin = t;
+ openfile->mark_begin_x = 0;
+ openfile->mark_set = TRUE;
+ }
+
+ openfile->mark_begin_x = u->mark_begin_x;
+ do_cut_text(FALSE, u->to_end, TRUE);
+ openfile->mark_set = FALSE;
+ openfile->mark_begin = NULL;
+ openfile->mark_begin_x = 0;
+ edit_refresh_needed = TRUE;
+}
+
+/* Undo the last thing(s) we did */
+void do_undo(void)
+{
+ undo *u = openfile->current_undo;
+ filestruct *f = openfile->current, *t;
+ int len = 0;
+ char *undidmsg, *data;
+ filestruct *oldcutbuffer = cutbuffer, *oldcutbottom = cutbottom;
+
+ if (!u) {
+ statusbar(_("Nothing in undo buffer!"));
+ return;
+ }
+
+
+ if (u->lineno <= f->lineno)
+ for (; f->prev != NULL && f->lineno != u->lineno; f = f->prev)
+ ;
+ else
+ for (; f->next != NULL && f->lineno != u->lineno; f = f->next)
+ ;
+ if (f->lineno != u->lineno) {
+ statusbar(_("Internal error: can't match line %d. Please save your work"), u->lineno);
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "data we're about to undo = \"%s\"\n", f->data);
+ fprintf(stderr, "Undo running for type %d\n", u->type);
+#endif
+
+ openfile->current_x = u->begin;
+ switch(u->type) {
+ case ADD:
+ undidmsg = _("text add");
+ len = strlen(f->data) - strlen(u->strdata) + 1;
+ data = charalloc(len);
+ strncpy(data, f->data, u->begin);
+ strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]);
+ free(f->data);
+ f->data = data;
+ break;
+ case DEL:
+ undidmsg = _("text delete");
+ len = strlen(f->data) + strlen(u->strdata) + 1;
+ data = charalloc(len);
+
+ strncpy(data, f->data, u->begin);
+ strcpy(&data[u->begin], u->strdata);
+ strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]);
+ free(f->data);
+ f->data = data;
+ if (u->xflags == UNdel_backspace)
+ openfile->current_x += strlen(u->strdata);
+ break;
+#ifndef DISABLE_WRAPPING
+ case SPLIT:
+ undidmsg = _("line wrap");
+ f->data = (char *) nrealloc(f->data, strlen(f->data) + strlen(u->strdata) + 1);
+ strcpy(&f->data[strlen(f->data) - 1], u->strdata);
+ if (u->strdata2 != NULL)
+ f->next->data = mallocstrcpy(f->next->data, u->strdata2);
+ else {
+ filestruct *foo = openfile->current->next;
+ unlink_node(foo);
+ delete_node(foo);
+ }
+ renumber(f);
+ break;
+#endif /* DISABLE_WRAPPING */
+ case UNSPLIT:
+ undidmsg = _("line join");
+ t = make_new_node(f);
+ t->data = mallocstrcpy(NULL, u->strdata);
+ data = mallocstrncpy(NULL, f->data, u->begin);
+ data[u->begin] = '\0';
+ free(f->data);
+ f->data = data;
+ splice_node(f, t, f->next);
+ renumber(f);
+ break;
+ case CUT:
+ undidmsg = _("text cut");
+ undo_cut(u);
+ break;
+ case UNCUT:
+ undidmsg = _("text uncut");
+ redo_cut(u);
+ break;
+ case ENTER:
+ undidmsg = _("line break");
+ if (f->next) {
+ filestruct *foo = f->next;
+ f->data = (char *) nrealloc(f->data, strlen(f->data) + strlen(f->next->data) + 1);
+ strcat(f->data, f->next->data);
+ unlink_node(foo);
+ delete_node(foo);
+ }
+ break;
+ case INSERT:
+ undidmsg = _("text insert");
+ cutbuffer = NULL;
+ cutbottom = NULL;
+ /* When we updated mark_begin_lineno in update_undo, it was effectively how many line
+ were inserted due to being partitioned before read_file was called. So we
+ add its value here */
+ openfile->mark_begin = fsfromline(u->lineno + u->mark_begin_lineno - 1);
+ openfile->mark_begin_x = 0;
+ openfile->mark_set = TRUE;
+ do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE);
+ cut_marked();
+ u->cutbuffer = cutbuffer;
+ u->cutbottom = cutbottom;
+ cutbuffer = oldcutbuffer;
+ cutbottom = oldcutbottom;
+ openfile->mark_set = FALSE;
+ break;
+ case REPLACE:
+ undidmsg = _("text replace");
+ data = u->strdata;
+ u->strdata = f->data;
+ f->data = data;
+ break;
+
+ default:
+ undidmsg = _("Internal error: unknown type. Please save your work");
+ break;
+
+ }
+ renumber(f);
+ do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE);
+ statusbar(_("Undid action (%s)"), undidmsg);
+ openfile->current_undo = openfile->current_undo->next;
+ openfile->last_action = OTHER;
+}
+
+void do_redo(void)
+{
+ undo *u = openfile->undotop;
+ filestruct *f = openfile->current;
+ int len = 0;
+ char *undidmsg, *data;
+
+ for (; u != NULL && u->next != openfile->current_undo; u = u->next)
+ ;
+ if (!u) {
+ statusbar(_("Nothing to re-do!"));
+ return;
+ }
+ if (u->next != openfile->current_undo) {
+ statusbar(_("Internal error: Redo setup failed. Please save your work"));
+ return;
+ }
+
+ if (u->lineno <= f->lineno)
+ for (; f->prev != NULL && f->lineno != u->lineno; f = f->prev)
+ ;
+ else
+ for (; f->next != NULL && f->lineno != u->lineno; f = f->next)
+ ;
+ if (f->lineno != u->lineno) {
+ statusbar(_("Internal error: can't match line %d. Please save your work"), u->lineno);
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "data we're about to redo = \"%s\"\n", f->data);
+ fprintf(stderr, "Redo running for type %d\n", u->type);
+#endif
+
+ switch(u->type) {
+ case ADD:
+ undidmsg = _("text add");
+ len = strlen(f->data) + strlen(u->strdata) + 1;
+ data = charalloc(len);
+ strncpy(data, f->data, u->begin);
+ strcpy(&data[u->begin], u->strdata);
+ strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]);
+ free(f->data);
+ f->data = data;
+ break;
+ case DEL:
+ undidmsg = _("text delete");
+ len = strlen(f->data) + strlen(u->strdata) + 1;
+ data = charalloc(len);
+ strncpy(data, f->data, u->begin);
+ strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]);
+ free(f->data);
+ f->data = data;
+ break;
+ case ENTER:
+ undidmsg = _("line break");
+ do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE);
+ do_enter(TRUE);
+ break;
+#ifndef DISABLE_WRAPPING
+ case SPLIT:
+ undidmsg = _("line wrap");
+ if (u->xflags & UNsplit_madenew)
+ prepend_wrap = TRUE;
+ do_wrap(f, TRUE);
+ renumber(f);
+ break;
+#endif /* DISABLE_WRAPPING */
+ case UNSPLIT:
+ undidmsg = _("line join");
+ len = strlen(f->data) + strlen(u->strdata + 1);
+ data = charalloc(len);
+ strcpy(data, f->data);
+ strcat(data, u->strdata);
+ free(f->data);
+ f->data = data;
+ if (f->next != NULL) {
+ filestruct *tmp = f->next;
+ unlink_node(tmp);
+ delete_node(tmp);
+ }
+ renumber(f);
+ break;
+ case CUT:
+ undidmsg = _("text cut");
+ redo_cut(u);
+ break;
+ case UNCUT:
+ undidmsg = _("text uncut");
+ undo_cut(u);
+ break;
+ case REPLACE:
+ undidmsg = _("text replace");
+ data = u->strdata;
+ u->strdata = f->data;
+ f->data = data;
+ break;
+ case INSERT:
+ undidmsg = _("text insert");
+ do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE);
+ copy_from_filestruct(u->cutbuffer, u->cutbottom);
+ openfile->placewewant = xplustabs();
+ break;
+ default:
+ undidmsg = _("Internal error: unknown type. Please save your work");
+ break;
+
+ }
+ do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE);
+ statusbar(_("Redid action (%s)"), undidmsg);
+
+ openfile->current_undo = u;
+ openfile->last_action = OTHER;
+
+}
+#endif /* !NANO_TINY */
+
+/* Someone hits Enter *gasp!* */
+void do_enter(bool undoing)
+{
+ filestruct *newnode = make_new_node(openfile->current);
+ size_t extra = 0;
+
+ assert(openfile->current != NULL && openfile->current->data != NULL);
+
+#ifndef NANO_TINY
+ if (!undoing)
+ add_undo(ENTER);
+
+
+ /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
+ if (ISSET(AUTOINDENT)) {
+ /* If we are breaking the line in the indentation, the new
+ * indentation should have only current_x characters, and
+ * current_x should not change. */
+ extra = indent_length(openfile->current->data);
+ if (extra > openfile->current_x)
+ extra = openfile->current_x;
+ }
+#endif
+ newnode->data = charalloc(strlen(openfile->current->data +
+ openfile->current_x) + extra + 1);
+ strcpy(&newnode->data[extra], openfile->current->data +
+ openfile->current_x);
+#ifndef NANO_TINY
+ if (ISSET(AUTOINDENT)) {
+ strncpy(newnode->data, openfile->current->data, extra);
+ openfile->totsize += extra;
+ }
+#endif
+ null_at(&openfile->current->data, openfile->current_x);
+#ifndef NANO_TINY
+ if (openfile->mark_set && openfile->current ==
+ openfile->mark_begin && openfile->current_x <
+ openfile->mark_begin_x) {
+ openfile->mark_begin = newnode;
+ openfile->mark_begin_x += extra - openfile->current_x;
+ }
+#endif
+ openfile->current_x = extra;
+
+ if (openfile->current == openfile->filebot)
+ openfile->filebot = newnode;
+ splice_node(openfile->current, newnode,
+ openfile->current->next);
+
+ renumber(openfile->current);
+ openfile->current = newnode;
+
+ openfile->totsize++;
+ set_modified();
+
+ openfile->placewewant = xplustabs();
+
+ edit_refresh_needed = TRUE;
+}
+
+/* Need this again... */
+void do_enter_void(void) {
+ do_enter(FALSE);
+}
+
+#ifndef NANO_TINY
+/* Send a SIGKILL (unconditional kill) to the forked process in
+ * execute_command(). */
+RETSIGTYPE cancel_command(int signal)
+{
+ if (kill(pid, SIGKILL) == -1)
+ nperror("kill");
+}
+
+/* Execute command in a shell. Return TRUE on success. */
+bool execute_command(const char *command)
+{
+ int fd[2];
+ FILE *f;
+ char *shellenv;
+ struct sigaction oldaction, newaction;
+ /* Original and temporary handlers for SIGINT. */
+ bool sig_failed = FALSE;
+ /* Did sigaction() fail without changing the signal handlers? */
+
+ /* Make our pipes. */
+ if (pipe(fd) == -1) {
+ statusbar(_("Could not pipe"));
+ return FALSE;
+ }
+
+ /* Check $SHELL for the shell to use. If it isn't set, use
+ * /bin/sh. Note that $SHELL should contain only a path, with no
+ * arguments. */
+ shellenv = getenv("SHELL");
+ if (shellenv == NULL)
+ shellenv = (char *) "/bin/sh";
+
+ /* Fork a child. */
+ if ((pid = fork()) == 0) {
+ close(fd[0]);
+ dup2(fd[1], fileno(stdout));
+ dup2(fd[1], fileno(stderr));
+
+ /* If execl() returns at all, there was an error. */
+ execl(shellenv, tail(shellenv), "-c", command, NULL);
+ exit(0);
+ }
+
+ /* Continue as parent. */
+ close(fd[1]);
+
+ if (pid == -1) {
+ close(fd[0]);
+ statusbar(_("Could not fork"));
+ return FALSE;
+ }
+
+ /* Before we start reading the forked command's output, we set
+ * things up so that Ctrl-C will cancel the new process. */
+
+ /* Enable interpretation of the special control keys so that we get
+ * SIGINT when Ctrl-C is pressed. */
+ enable_signals();
+
+ if (sigaction(SIGINT, NULL, &newaction) == -1) {
+ sig_failed = TRUE;
+ nperror("sigaction");
+ } else {
+ newaction.sa_handler = cancel_command;
+ if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
+ sig_failed = TRUE;
+ nperror("sigaction");
+ }
+ }
+
+ /* Note that now oldaction is the previous SIGINT signal handler,
+ * to be restored later. */
+
+ f = fdopen(fd[0], "rb");
+ if (f == NULL)
+ nperror("fdopen");
+
+ read_file(f, 0, "stdin", TRUE, FALSE);
+
+ if (wait(NULL) == -1)
+ nperror("wait");
+
+ if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
+ nperror("sigaction");
+
+ /* Restore the terminal to its previous state. In the process,
+ * disable interpretation of the special control keys so that we can
+ * use Ctrl-C for other things. */
+ terminal_init();
+
+ return TRUE;
+}
+
+/* Add a new undo struct to the top of the current pile */
+void add_undo(undo_type current_action)
+{
+ undo *u;
+ char *data;
+ openfilestruct *fs = openfile;
+ static undo *last_cutu = NULL; /* Last thing we cut to set up the undo for uncut */
+ ssize_t wrap_loc; /* For calculating split beginning */
+
+ if (!ISSET(UNDOABLE))
+ return;
+
+ /* Ugh, if we were called while cutting not-to-end, non-marked and on the same lineno,
+ we need to abort here */
+ u = fs->current_undo;
+ if (current_action == CUT && u && u->type == CUT
+ && !u->mark_set && u->lineno == fs->current->lineno)
+ return;
+
+ /* Blow away the old undo stack if we are starting from the middle */
+ while (fs->undotop != NULL && fs->undotop != fs->current_undo) {
+ undo *u2 = fs->undotop;
+ fs->undotop = fs->undotop->next;
+ if (u2->strdata != NULL)
+ free(u2->strdata);
+ if (u2->cutbuffer)
+ free_filestruct(u2->cutbuffer);
+ free(u2);
+ }
+
+ /* Allocate and initialize a new undo type */
+ u = (undo *) nmalloc(sizeof(undo));
+ u->type = current_action;
+ u->lineno = fs->current->lineno;
+ u->begin = fs->current_x;
+ u->next = fs->undotop;
+ fs->undotop = u;
+ fs->current_undo = u;
+ u->strdata = NULL;
+ u->strdata2 = NULL;
+ u->cutbuffer = NULL;
+ u->cutbottom = NULL;
+ u->mark_set = 0;
+ u->mark_begin_lineno = 0;
+ u->mark_begin_x = 0;
+ u->xflags = 0;
+ u->to_end = FALSE;
+
+ switch (u->type) {
+ /* We need to start copying data into the undo buffer or we wont be able
+ to restore it later */
+ case ADD:
+ data = charalloc(2);
+ data[0] = fs->current->data[fs->current_x];
+ data[1] = '\0';
+ u->strdata = data;
+ break;
+ case DEL:
+ if (u->begin != strlen(fs->current->data)) {
+ data = mallocstrncpy(NULL, &fs->current->data[u->begin], 2);
+ data[1] = '\0';
+ u->strdata = data;
+ break;
+ }
+ /* Else purposely fall into unsplit code */
+ current_action = u->type = UNSPLIT;
+ case UNSPLIT:
+ if (fs->current->next) {
+ data = mallocstrcpy(NULL, fs->current->next->data);
+ u->strdata = data;
+ }
+ break;
+#ifndef DISABLE_WRAPPING
+ case SPLIT:
+ wrap_loc = break_line(openfile->current->data, fill
+#ifndef DISABLE_HELP
+ , FALSE
+#endif
+ );
+ u->strdata = mallocstrcpy(NULL, &openfile->current->data[wrap_loc]);
+ /* Don't both saving the next line if we're not prepending as a new line
+ will be created */
+ if (prepend_wrap)
+ u->strdata2 = mallocstrcpy(NULL, fs->current->next->data);
+ u->begin = wrap_loc;
+ break;
+#endif /* DISABLE_WRAPPING */
+ case INSERT:
+ case REPLACE:
+ data = mallocstrcpy(NULL, fs->current->data);
+ u->strdata = data;
+ break;
+ case CUT:
+ u->mark_set = openfile->mark_set;
+ if (u->mark_set) {
+ u->mark_begin_lineno = openfile->mark_begin->lineno;
+ u->mark_begin_x = openfile->mark_begin_x;
+ }
+ u->to_end = (ISSET(CUT_TO_END)) ? TRUE : FALSE;
+ last_cutu = u;
+ break;
+ case UNCUT:
+ if (!last_cutu)
+ statusbar(_("Internal error: can't setup uncut. Please save your work."));
+ else if (last_cutu->type == CUT) {
+ u->cutbuffer = last_cutu->cutbuffer;
+ u->cutbottom = last_cutu->cutbottom;
+ }
+ break;
+ case ENTER:
+ break;
+ case OTHER:
+ statusbar(_("Internal error: unknown type. Please save your work."));
+ break;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "fs->current->data = \"%s\", current_x = %lu, u->begin = %d, type = %d\n",
+ fs->current->data, (unsigned long) fs->current_x, u->begin, current_action);
+ fprintf(stderr, "left add_undo...\n");
+#endif
+ fs->last_action = current_action;
+}
+
+/* Update an undo item, or determine whether a new one
+ is really needed and bounce the data to add_undo
+ instead. The latter functionality just feels
+ gimmicky and may just be more hassle than
+ it's worth, so it should be axed if needed. */
+void update_undo(undo_type action)
+{
+ undo *u;
+ char *data;
+ int len = 0;
+ openfilestruct *fs = openfile;
+
+ if (!ISSET(UNDOABLE))
+ return;
+
+#ifdef DEBUG
+ fprintf(stderr, "action = %d, fs->last_action = %d, openfile->current->lineno = %lu",
+ action, fs->last_action, (unsigned long) openfile->current->lineno);
+ if (fs->current_undo)
+ fprintf(stderr, "fs->current_undo->lineno = %lu\n", (unsigned long) fs->current_undo->lineno);
+ else
+ fprintf(stderr, "\n");
+#endif
+
+ /* Change to an add if we're not using the same undo struct
+ that we should be using */
+ if (action != fs->last_action
+ || (action != CUT && action != INSERT && action != SPLIT
+ && openfile->current->lineno != fs->current_undo->lineno)) {
+ add_undo(action);
+ return;
+ }
+
+ assert(fs->undotop != NULL);
+ u = fs->undotop;
+
+ switch (u->type) {
+ case ADD:
+#ifdef DEBUG
+ fprintf(stderr, "fs->current->data = \"%s\", current_x = %lu, u->begin = %d\n",
+ fs->current->data, (unsigned long) fs->current_x, u->begin);
+#endif
+ len = strlen(u->strdata) + 2;
+ data = (char *) nrealloc((void *) u->strdata, len * sizeof(char *));
+ data[len-2] = fs->current->data[fs->current_x];
+ data[len-1] = '\0';
+ u->strdata = (char *) data;
+#ifdef DEBUG
+ fprintf(stderr, "current undo data now \"%s\"\n", u->strdata);
+#endif
+ break;
+ case DEL:
+ len = strlen(u->strdata) + 2;
+ assert(len > 2);
+ if (fs->current_x == u->begin) {
+ /* They're deleting */
+ if (!u->xflags)
+ u->xflags = UNdel_del;
+ else if (u->xflags != UNdel_del) {
+ add_undo(action);
+ return;
+ }
+ data = charalloc(len);
+ strcpy(data, u->strdata);
+ data[len-2] = fs->current->data[fs->current_x];;
+ data[len-1] = '\0';
+ free(u->strdata);
+ u->strdata = data;
+ } else if (fs->current_x == u->begin - 1) {
+ /* They're backspacing */
+ if (!u->xflags)
+ u->xflags = UNdel_backspace;
+ else if (u->xflags != UNdel_backspace) {
+ add_undo(action);
+ return;
+ }
+ data = charalloc(len);
+ data[0] = fs->current->data[fs->current_x];
+ strcpy(&data[1], u->strdata);
+ free(u->strdata);
+ u->strdata = data;
+ u->begin--;
+ } else {
+ /* They deleted something else on the line */
+ add_undo(DEL);
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "current undo data now \"%s\"\nu->begin = %d\n", u->strdata, u->begin);
+#endif
+ break;
+ case CUT:
+ if (!cutbuffer)
+ break;
+ if (u->cutbuffer)
+ free(u->cutbuffer);
+ u->cutbuffer = copy_filestruct(cutbuffer);
+ /* Compute cutbottom for the uncut using out copy */
+ for (u->cutbottom = u->cutbuffer; u->cutbottom->next != NULL; u->cutbottom = u->cutbottom->next)
+ ;
+ break;
+ case REPLACE:
+ case UNCUT:
+ add_undo(action);
+ break;
+ case INSERT:
+ u->mark_begin_lineno = openfile->current->lineno;
+ break;
+#ifndef DISABLE_WRAPPING
+ case SPLIT:
+ /* This will only be called if we made a completely new line,
+ and as such we should note that so we can destroy it later */
+ u->xflags = UNsplit_madenew;
+ break;
+#endif /* DISABLE_WRAPPING */
+ case UNSPLIT:
+ /* These cases are handled by the earlier check for a new line and action */
+ case ENTER:
+ case OTHER:
+ break;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Done in udpate_undo (type was %d)\n", action);
+#endif
+ if (fs->last_action != action) {
+#ifdef DEBUG
+ fprintf(stderr, "Starting add_undo for new action as it does not match last_action\n");
+#endif
+ add_undo(action);
+ }
+ fs->last_action = action;
+}
+
+#endif /* !NANO_TINY */
+
+#ifndef DISABLE_WRAPPING
+/* Unset the prepend_wrap flag. We need to do this as soon as we do
+ * something other than type text. */
+void wrap_reset(void)
+{
+ prepend_wrap = FALSE;
+}
+
+/* We wrap the given line. Precondition: we assume the cursor has been
+ * moved forward since the last typed character. Return TRUE if we
+ * wrapped, and FALSE otherwise. */
+bool do_wrap(filestruct *line, bool undoing)
+{
+ size_t line_len;
+ /* The length of the line we wrap. */
+ ssize_t wrap_loc;
+ /* The index of line->data where we wrap. */
+#ifndef NANO_TINY
+ const char *indent_string = NULL;
+ /* Indentation to prepend to the new line. */
+ size_t indent_len = 0;
+ /* The length of indent_string. */
+#endif
+ const char *after_break;
+ /* The text after the wrap point. */
+ size_t after_break_len;
+ /* The length of after_break. */
+ bool prepending = FALSE;
+ /* Do we prepend to the next line? */
+ const char *next_line = NULL;
+ /* The next line, minus indentation. */
+ size_t next_line_len = 0;
+ /* The length of next_line. */
+ char *new_line = NULL;
+ /* The line we create. */
+ size_t new_line_len = 0;
+ /* The eventual length of new_line. */
+
+ /* There are three steps. First, we decide where to wrap. Then, we
+ * create the new wrap line. Finally, we clean up. */
+
+ /* Step 1, finding where to wrap. We are going to add a new line
+ * after a blank character. In this step, we call break_line() to
+ * get the location of the last blank we can break the line at, and
+ * set wrap_loc to the location of the character after it, so that
+ * the blank is preserved at the end of the line.
+ *
+ * If there is no legal wrap point, or we reach the last character
+ * of the line while trying to find one, we should return without
+ * wrapping. Note that if autoindent is turned on, we don't break
+ * at the end of it! */
+ assert(line != NULL && line->data != NULL);
+
+ /* Save the length of the line. */
+ line_len = strlen(line->data);
+
+ /* Find the last blank where we can break the line. */
+ wrap_loc = break_line(line->data, fill
+#ifndef DISABLE_HELP
+ , FALSE
+#endif
+ );
+
+ /* If we couldn't break the line, or we've reached the end of it, we
+ * don't wrap. */
+ if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
+ return FALSE;
+
+ /* Otherwise, move forward to the character just after the blank. */
+ wrap_loc += move_mbright(line->data + wrap_loc, 0);
+
+ /* If we've reached the end of the line, we don't wrap. */
+ if (line->data[wrap_loc] == '\0')
+ return FALSE;
+
+#ifndef NANO_TINY
+ if (!undoing)
+ add_undo(SPLIT);
+
+ /* If autoindent is turned on, and we're on the character just after
+ * the indentation, we don't wrap. */
+ if (ISSET(AUTOINDENT)) {
+ /* Get the indentation of this line. */
+ indent_string = line->data;
+ indent_len = indent_length(indent_string);
+
+ if (wrap_loc == indent_len)
+ return FALSE;
+ }
+#endif
+
+ /* Step 2, making the new wrap line. It will consist of indentation
+ * followed by the text after the wrap point, optionally followed by
+ * a space (if the text after the wrap point doesn't end in a blank)
+ * and the text of the next line, if they can fit without wrapping,
+ * the next line exists, and the prepend_wrap flag is set. */
+
+ /* after_break is the text that will be wrapped to the next line. */
+ after_break = line->data + wrap_loc;
+ after_break_len = line_len - wrap_loc;
+
+ assert(strlen(after_break) == after_break_len);
+
+ /* We prepend the wrapped text to the next line, if the prepend_wrap
+ * flag is set, there is a next line, and prepending would not make
+ * the line too long. */
+ if (prepend_wrap && line != openfile->filebot) {
+ const char *end = after_break + move_mbleft(after_break,
+ after_break_len);
+
+ /* If after_break doesn't end in a blank, make sure it ends in a
+ * space. */
+ if (!is_blank_mbchar(end)) {
+ line_len++;
+ line->data = charealloc(line->data, line_len + 1);
+ line->data[line_len - 1] = ' ';
+ line->data[line_len] = '\0';
+ after_break = line->data + wrap_loc;
+ after_break_len++;
+ openfile->totsize++;
+ }
+
+ next_line = line->next->data;
+ next_line_len = strlen(next_line);
+
+ if (after_break_len + next_line_len <= fill) {
+ prepending = TRUE;
+ new_line_len += next_line_len;
+ }
+ }
+
+ /* new_line_len is now the length of the text that will be wrapped
+ * to the next line, plus (if we're prepending to it) the length of
+ * the text of the next line. */
+ new_line_len += after_break_len;
+
+#ifndef NANO_TINY
+ if (ISSET(AUTOINDENT)) {
+ if (prepending) {
+ /* If we're prepending, the indentation will come from the
+ * next line. */
+ indent_string = next_line;
+ indent_len = indent_length(indent_string);
+ next_line += indent_len;
+ } else {
+ /* Otherwise, it will come from this line, in which case
+ * we should increase new_line_len to make room for it. */
+ new_line_len += indent_len;
+ openfile->totsize += mbstrnlen(indent_string, indent_len);
+ }
+ }
+#endif
+
+ /* Now we allocate the new line and copy the text into it. */
+ new_line = charalloc(new_line_len + 1);
+ new_line[0] = '\0';
+
+#ifndef NANO_TINY
+ if (ISSET(AUTOINDENT)) {
+ /* Copy the indentation. */
+ strncpy(new_line, indent_string, indent_len);
+ new_line[indent_len] = '\0';
+ new_line_len += indent_len;
+ }
+#endif
+
+ /* Copy all the text after the wrap point of the current line. */
+ strcat(new_line, after_break);
+
+ /* Break the current line at the wrap point. */
+ null_at(&line->data, wrap_loc);
+
+ if (prepending) {
+ if (!undoing)
+ update_undo(SPLIT);
+ /* If we're prepending, copy the text from the next line, minus
+ * the indentation that we already copied above. */
+ strcat(new_line, next_line);
+
+ free(line->next->data);
+ line->next->data = new_line;
+
+ /* If the NO_NEWLINES flag isn't set, and text has been added to
+ * the magicline, make a new magicline. */
+ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
+ new_magicline();
+ } else {
+ /* Otherwise, make a new line and copy the text after where we
+ * broke this line to the beginning of the new line. */
+ splice_node(openfile->current, make_new_node(openfile->current),
+ openfile->current->next);
+
+ /* If the current line is the last line of the file, move the
+ * last line of the file down to the next line. */
+ if (openfile->filebot == openfile->current)
+ openfile->filebot = openfile->current->next;
+
+ openfile->current->next->data = new_line;
+
+ openfile->totsize++;
+ }
+
+ /* Step 3, clean up. Reposition the cursor and mark, and do some
+ * other sundry things. */
+
+ /* Set the prepend_wrap flag, so that later wraps of this line will
+ * be prepended to the next line. */
+ prepend_wrap = TRUE;
+
+ /* Each line knows its number. We recalculate these if we inserted
+ * a new line. */
+ if (!prepending)
+ renumber(line);
+
+ /* If the cursor was after the break point, we must move it. We
+ * also clear the prepend_wrap flag in this case. */
+ if (openfile->current_x > wrap_loc) {
+ prepend_wrap = FALSE;
+
+ openfile->current = openfile->current->next;
+ openfile->current_x -= wrap_loc
+#ifndef NANO_TINY
+ - indent_len
+#endif
+ ;
+ openfile->placewewant = xplustabs();
+ }
+
+#ifndef NANO_TINY
+ /* If the mark was on this line after the wrap point, we move it
+ * down. If it was on the next line and we prepended to that line,
+ * we move it right. */
+ if (openfile->mark_set) {
+ if (openfile->mark_begin == line && openfile->mark_begin_x >
+ wrap_loc) {
+ openfile->mark_begin = line->next;
+ openfile->mark_begin_x -= wrap_loc - indent_len + 1;
+ } else if (prepending && openfile->mark_begin == line->next)
+ openfile->mark_begin_x += after_break_len;
+ }
+#endif
+
+ return TRUE;
+}
+#endif /* !DISABLE_WRAPPING */
+
+#if !defined(DISABLE_HELP) || !defined(DISABLE_WRAPJUSTIFY)
+/* We are trying to break a chunk off line. We find the last blank such
+ * that the display length to there is at most (goal + 1). If there is
+ * no such blank, then we find the first blank. We then take the last
+ * blank in that group of blanks. The terminating '\0' counts as a
+ * blank, as does a '\n' if newline is TRUE. */
+ssize_t break_line(const char *line, ssize_t goal
+#ifndef DISABLE_HELP
+ , bool newln
+#endif
+ )
+{
+ ssize_t blank_loc = -1;
+ /* Current tentative return value. Index of the last blank we
+ * found with short enough display width. */
+ ssize_t cur_loc = 0;
+ /* Current index in line. */
+ size_t cur_pos = 0;
+ /* Current column position in line. */
+ int line_len;
+
+ assert(line != NULL);
+
+ while (*line != '\0' && goal >= cur_pos) {
+ line_len = parse_mbchar(line, NULL, &cur_pos);
+
+ if (is_blank_mbchar(line)
+#ifndef DISABLE_HELP
+ || (newln && *line == '\n')
+#endif
+ ) {
+ blank_loc = cur_loc;
+
+#ifndef DISABLE_HELP
+ if (newln && *line == '\n')
+ break;
+#endif
+ }
+
+ line += line_len;
+ cur_loc += line_len;
+ }
+
+ if (goal >= cur_pos)
+ /* In fact, the whole line displays shorter than goal. */
+ return cur_loc;
+
+#ifndef DISABLE_HELP
+ if (newln && blank_loc <= 0) {
+ /* If blank was not found or was found only first character,
+ * force line break. */
+ cur_loc -= line_len;
+ return cur_loc;
+ }
+#endif
+
+ if (blank_loc == -1) {
+ /* No blank was found that was short enough. */
+ bool found_blank = FALSE;
+ ssize_t found_blank_loc = 0;
+
+ while (*line != '\0') {
+ line_len = parse_mbchar(line, NULL, NULL);
+
+ if (is_blank_mbchar(line)
+#ifndef DISABLE_HELP
+ || (newln && *line == '\n')
+#endif
+ ) {
+ if (!found_blank)
+ found_blank = TRUE;
+ found_blank_loc = cur_loc;
+ } else if (found_blank)
+ return found_blank_loc;
+
+ line += line_len;
+ cur_loc += line_len;
+ }
+
+ return -1;
+ }
+
+ /* Move to the last blank after blank_loc, if there is one. */
+ line -= cur_loc;
+ line += blank_loc;
+ line_len = parse_mbchar(line, NULL, NULL);
+ line += line_len;
+
+ while (*line != '\0' && (is_blank_mbchar(line)
+#ifndef DISABLE_HELP
+ || (newln && *line == '\n')
+#endif
+ )) {
+#ifndef DISABLE_HELP
+ if (newln && *line == '\n')
+ break;
+#endif
+
+ line_len = parse_mbchar(line, NULL, NULL);
+
+ line += line_len;
+ blank_loc += line_len;
+ }
+
+ return blank_loc;
+}
+#endif /* !DISABLE_HELP || !DISABLE_WRAPJUSTIFY */
+
+#if !defined(NANO_TINY) || !defined(DISABLE_JUSTIFY)
+/* The "indentation" of a line is the whitespace between the quote part
+ * and the non-whitespace of the line. */
+size_t indent_length(const char *line)
+{
+ size_t len = 0;
+ char *blank_mb;
+ int blank_mb_len;
+
+ assert(line != NULL);
+
+ blank_mb = charalloc(mb_cur_max());
+
+ while (*line != '\0') {
+ blank_mb_len = parse_mbchar(line, blank_mb, NULL);
+
+ if (!is_blank_mbchar(blank_mb))
+ break;
+
+ line += blank_mb_len;
+ len += blank_mb_len;
+ }
+
+ free(blank_mb);
+
+ return len;
+}
+#endif /* !NANO_TINY || !DISABLE_JUSTIFY */
+
+#ifndef DISABLE_JUSTIFY
+/* justify_format() replaces blanks with spaces and multiple spaces by 1
+ * (except it maintains up to 2 after a character in punct optionally
+ * followed by a character in brackets, and removes all from the end).
+ *
+ * justify_format() might make paragraph->data shorter, and change the
+ * actual pointer with null_at().
+ *
+ * justify_format() will not look at the first skip characters of
+ * paragraph. skip should be at most strlen(paragraph->data). The
+ * character at paragraph[skip + 1] must not be blank. */
+void justify_format(filestruct *paragraph, size_t skip)
+{
+ char *end, *new_end, *new_paragraph_data;
+ size_t shift = 0;
+#ifndef NANO_TINY
+ size_t mark_shift = 0;
+#endif
+
+ /* These four asserts are assumptions about the input data. */
+ assert(paragraph != NULL);
+ assert(paragraph->data != NULL);
+ assert(skip < strlen(paragraph->data));
+ assert(!is_blank_mbchar(paragraph->data + skip));
+
+ end = paragraph->data + skip;
+ new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
+ strncpy(new_paragraph_data, paragraph->data, skip);
+ new_end = new_paragraph_data + skip;
+
+ while (*end != '\0') {
+ int end_len;
+
+ /* If this character is blank, change it to a space if
+ * necessary, and skip over all blanks after it. */
+ if (is_blank_mbchar(end)) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ *new_end = ' ';
+ new_end++;
+ end += end_len;
+
+ while (*end != '\0' && is_blank_mbchar(end)) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ end += end_len;
+ shift += end_len;
+
+#ifndef NANO_TINY
+ /* Keep track of the change in the current line. */
+ if (openfile->mark_set && openfile->mark_begin ==
+ paragraph && openfile->mark_begin_x >= end -
+ paragraph->data)
+ mark_shift += end_len;
+#endif
+ }
+ /* If this character is punctuation optionally followed by a
+ * bracket and then followed by blanks, change no more than two
+ * of the blanks to spaces if necessary, and skip over all
+ * blanks after them. */
+ } else if (mbstrchr(punct, end) != NULL) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ while (end_len > 0) {
+ *new_end = *end;
+ new_end++;
+ end++;
+ end_len--;
+ }
+
+ if (*end != '\0' && mbstrchr(brackets, end) != NULL) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ while (end_len > 0) {
+ *new_end = *end;
+ new_end++;
+ end++;
+ end_len--;
+ }
+ }
+
+ if (*end != '\0' && is_blank_mbchar(end)) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ *new_end = ' ';
+ new_end++;
+ end += end_len;
+ }
+
+ if (*end != '\0' && is_blank_mbchar(end)) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ *new_end = ' ';
+ new_end++;
+ end += end_len;
+ }
+
+ while (*end != '\0' && is_blank_mbchar(end)) {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ end += end_len;
+ shift += end_len;
+
+#ifndef NANO_TINY
+ /* Keep track of the change in the current line. */
+ if (openfile->mark_set && openfile->mark_begin ==
+ paragraph && openfile->mark_begin_x >= end -
+ paragraph->data)
+ mark_shift += end_len;
+#endif
+ }
+ /* If this character is neither blank nor punctuation, leave it
+ * unchanged. */
+ } else {
+ end_len = parse_mbchar(end, NULL, NULL);
+
+ while (end_len > 0) {
+ *new_end = *end;
+ new_end++;
+ end++;
+ end_len--;
+ }
+ }
+ }
+
+ assert(*end == '\0');
+
+ *new_end = *end;
+
+ /* If there are spaces at the end of the line, remove them. */
+ while (new_end > new_paragraph_data + skip &&
+ *(new_end - 1) == ' ') {
+ new_end--;
+ shift++;
+ }
+
+ if (shift > 0) {
+ openfile->totsize -= shift;
+ null_at(&new_paragraph_data, new_end - new_paragraph_data);
+ free(paragraph->data);
+ paragraph->data = new_paragraph_data;
+
+#ifndef NANO_TINY
+ /* Adjust the mark coordinates to compensate for the change in
+ * the current line. */
+ if (openfile->mark_set && openfile->mark_begin == paragraph) {
+ openfile->mark_begin_x -= mark_shift;
+ if (openfile->mark_begin_x > new_end - new_paragraph_data)
+ openfile->mark_begin_x = new_end - new_paragraph_data;
+ }
+#endif
+ } else
+ free(new_paragraph_data);
+}
+
+/* The "quote part" of a line is the largest initial substring matching
+ * the quote string. This function returns the length of the quote part
+ * of the given line.
+ *
+ * Note that if !HAVE_REGEX_H then we match concatenated copies of
+ * quotestr. */
+size_t quote_length(const char *line)
+{
+#ifdef HAVE_REGEX_H
+ regmatch_t matches;
+ int rc = regexec(&quotereg, line, 1, &matches, 0);
+
+ if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
+ return 0;
+ /* matches.rm_so should be 0, since the quote string should start
+ * with the caret ^. */
+ return matches.rm_eo;
+#else /* !HAVE_REGEX_H */
+ size_t qdepth = 0;
+
+ /* Compute quote depth level. */
+ while (strncmp(line + qdepth, quotestr, quotelen) == 0)
+ qdepth += quotelen;
+ return qdepth;
+#endif /* !HAVE_REGEX_H */
+}
+
+/* a_line and b_line are lines of text. The quotation part of a_line is
+ * the first a_quote characters. Check that the quotation part of
+ * b_line is the same. */
+bool quotes_match(const char *a_line, size_t a_quote, const char
+ *b_line)
+{
+ /* Here is the assumption about a_quote. */
+ assert(a_quote == quote_length(a_line));
+
+ return (a_quote == quote_length(b_line) &&
+ strncmp(a_line, b_line, a_quote) == 0);
+}
+
+/* We assume a_line and b_line have no quote part. Then, we return
+ * whether b_line could follow a_line in a paragraph. */
+bool indents_match(const char *a_line, size_t a_indent, const char
+ *b_line, size_t b_indent)
+{
+ assert(a_indent == indent_length(a_line));
+ assert(b_indent == indent_length(b_line));
+
+ return (b_indent <= a_indent &&
+ strncmp(a_line, b_line, b_indent) == 0);
+}
+
+/* Is foo the beginning of a paragraph?
+ *
+ * A line of text consists of a "quote part", followed by an
+ * "indentation part", followed by text. The functions quote_length()
+ * and indent_length() calculate these parts.
+ *
+ * A line is "part of a paragraph" if it has a part not in the quote
+ * part or the indentation.
+ *
+ * A line is "the beginning of a paragraph" if it is part of a
+ * paragraph and
+ * 1) it is the top line of the file, or
+ * 2) the line above it is not part of a paragraph, or
+ * 3) the line above it does not have precisely the same quote
+ * part, or
+ * 4) the indentation of this line is not an initial substring of
+ * the indentation of the previous line, or
+ * 5) this line has no quote part and some indentation, and
+ * autoindent isn't turned on.
+ * The reason for number 5) is that if autoindent isn't turned on,
+ * then an indented line is expected to start a paragraph, as in
+ * books. Thus, nano can justify an indented paragraph only if
+ * autoindent is turned on. */
+bool begpar(const filestruct *const foo)
+{
+ size_t quote_len, indent_len, temp_id_len;
+
+ if (foo == NULL)
+ return FALSE;
+
+ /* Case 1). */
+ if (foo == openfile->fileage)
+ return TRUE;
+
+ quote_len = quote_length(foo->data);
+ indent_len = indent_length(foo->data + quote_len);
+
+ /* Not part of a paragraph. */
+ if (foo->data[quote_len + indent_len] == '\0')
+ return FALSE;
+
+ /* Case 3). */
+ if (!quotes_match(foo->data, quote_len, foo->prev->data))
+ return TRUE;
+
+ temp_id_len = indent_length(foo->prev->data + quote_len);
+
+ /* Case 2) or 5) or 4). */
+ if (foo->prev->data[quote_len + temp_id_len] == '\0' ||
+ (quote_len == 0 && indent_len > 0
+#ifndef NANO_TINY
+ && !ISSET(AUTOINDENT)
+#endif
+ ) || !indents_match(foo->prev->data + quote_len, temp_id_len,
+ foo->data + quote_len, indent_len))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Is foo inside a paragraph? */
+bool inpar(const filestruct *const foo)
+{
+ size_t quote_len;
+
+ if (foo == NULL)
+ return FALSE;
+
+ quote_len = quote_length(foo->data);
+
+ return (foo->data[quote_len + indent_length(foo->data +
+ quote_len)] != '\0');
+}
+
+/* Move the next par_len lines, starting with first_line, into the
+ * justify buffer, leaving copies of those lines in place. Assume that
+ * par_len is greater than zero, and that there are enough lines after
+ * first_line. */
+void backup_lines(filestruct *first_line, size_t par_len)
+{
+ filestruct *top = first_line;
+ /* The top of the paragraph we're backing up. */
+ filestruct *bot = first_line;
+ /* The bottom of the paragraph we're backing up. */
+ size_t i;
+ /* Generic loop variable. */
+ size_t current_x_save = openfile->current_x;
+ ssize_t fl_lineno_save = first_line->lineno;
+ ssize_t edittop_lineno_save = openfile->edittop->lineno;
+ ssize_t current_lineno_save = openfile->current->lineno;
+#ifndef NANO_TINY
+ bool old_mark_set = openfile->mark_set;
+ ssize_t mb_lineno_save = 0;
+ size_t mark_begin_x_save = 0;
+
+ if (old_mark_set) {
+ mb_lineno_save = openfile->mark_begin->lineno;
+ mark_begin_x_save = openfile->mark_begin_x;
+ }
+#endif
+
+ /* par_len will be one greater than the number of lines between
+ * current and filebot if filebot is the last line in the
+ * paragraph. */
+ assert(par_len > 0 && openfile->current->lineno + par_len <=
+ openfile->filebot->lineno + 1);
+
+ /* Move bot down par_len lines to the line after the last line of
+ * the paragraph, if there is one. */
+ for (i = par_len; i > 0 && bot != openfile->filebot; i--)
+ bot = bot->next;
+
+ /* Move the paragraph from the current buffer's filestruct to the
+ * justify buffer. */
+ move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot,
+ (i == 1 && bot == openfile->filebot) ? strlen(bot->data) : 0);
+
+ /* Copy the paragraph back to the current buffer's filestruct from
+ * the justify buffer. */
+ copy_from_filestruct(jusbuffer, jusbottom);
+
+ /* Move upward from the last line of the paragraph to the first
+ * line, putting first_line, edittop, current, and mark_begin at the
+ * same lines in the copied paragraph that they had in the original
+ * paragraph. */
+ if (openfile->current != openfile->fileage) {
+ top = openfile->current->prev;
+#ifndef NANO_TINY
+ if (old_mark_set &&
+ openfile->current->lineno == mb_lineno_save) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x = mark_begin_x_save;
+ }
+#endif
+ } else
+ top = openfile->current;
+ for (i = par_len; i > 0 && top != NULL; i--) {
+ if (top->lineno == fl_lineno_save)
+ first_line = top;
+ if (top->lineno == edittop_lineno_save)
+ openfile->edittop = top;
+ if (top->lineno == current_lineno_save)
+ openfile->current = top;
+#ifndef NANO_TINY
+ if (old_mark_set && top->lineno == mb_lineno_save) {
+ openfile->mark_begin = top;
+ openfile->mark_begin_x = mark_begin_x_save;
+ }
+#endif
+ top = top->prev;
+ }
+
+ /* Put current_x at the same place in the copied paragraph that it
+ * had in the original paragraph. */
+ openfile->current_x = current_x_save;
+
+ set_modified();
+}
+
+/* Find the beginning of the current paragraph if we're in one, or the
+ * beginning of the next paragraph if we're not. Afterwards, save the
+ * quote length and paragraph length in *quote and *par. Return TRUE if
+ * we found a paragraph, and FALSE if there was an error or we didn't
+ * find a paragraph.
+ *
+ * See the comment at begpar() for more about when a line is the
+ * beginning of a paragraph. */
+bool find_paragraph(size_t *const quote, size_t *const par)
+{
+ size_t quote_len;
+ /* Length of the initial quotation of the paragraph we search
+ * for. */
+ size_t par_len;
+ /* Number of lines in the paragraph we search for. */
+ filestruct *current_save;
+ /* The line at the beginning of the paragraph we search for. */
+ ssize_t current_y_save;
+ /* The y-coordinate at the beginning of the paragraph we search
+ * for. */
+
+#ifdef HAVE_REGEX_H
+ if (quoterc != 0) {
+ statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
+ return FALSE;
+ }
+#endif
+
+ assert(openfile->current != NULL);
+
+ /* If we're at the end of the last line of the file, it means that
+ * there aren't any paragraphs left, so get out. */
+ if (openfile->current == openfile->filebot && openfile->current_x ==
+ strlen(openfile->filebot->data))
+ return FALSE;
+
+ /* If the current line isn't in a paragraph, move forward to the
+ * last line of the next paragraph, if any. */
+ if (!inpar(openfile->current)) {
+ do_para_end(FALSE);
+
+ /* If we end up past the beginning of the line, it means that
+ * we're at the end of the last line of the file, and the line
+ * isn't blank, in which case the last line of the file is the
+ * last line of the next paragraph.
+ *
+ * Otherwise, if we end up on a line that's in a paragraph, it
+ * means that we're on the line after the last line of the next
+ * paragraph, in which case we should move back to the last line
+ * of the next paragraph. */
+ if (openfile->current_x == 0) {
+ if (!inpar(openfile->current->prev))
+ return FALSE;
+ if (openfile->current != openfile->fileage)
+ openfile->current = openfile->current->prev;
+ }
+ }
+
+ /* If the current line isn't the first line of the paragraph, move
+ * back to the first line of the paragraph. */
+ if (!begpar(openfile->current))
+ do_para_begin(FALSE);
+
+ /* Now current is the first line of the paragraph. Set quote_len to
+ * the quotation length of that line, and set par_len to the number
+ * of lines in this paragraph. */
+ quote_len = quote_length(openfile->current->data);
+ current_save = openfile->current;
+ current_y_save = openfile->current_y;
+ do_para_end(FALSE);
+ par_len = openfile->current->lineno - current_save->lineno;
+
+ /* If we end up past the beginning of the line, it means that we're
+ * at the end of the last line of the file, and the line isn't
+ * blank, in which case the last line of the file is part of the
+ * paragraph. */
+ if (openfile->current_x > 0)
+ par_len++;
+ openfile->current = current_save;
+ openfile->current_y = current_y_save;
+
+ /* Save the values of quote_len and par_len. */
+ assert(quote != NULL && par != NULL);
+
+ *quote = quote_len;
+ *par = par_len;
+
+ return TRUE;
+}
+
+/* If full_justify is TRUE, justify the entire file. Otherwise, justify
+ * the current paragraph. */
+void do_justify(bool full_justify)
+{
+ filestruct *first_par_line = NULL;
+ /* Will be the first line of the justified paragraph(s), if any.
+ * For restoring after unjustify. */
+ filestruct *last_par_line = NULL;
+ /* Will be the line after the last line of the justified
+ * paragraph(s), if any. Also for restoring after unjustify. */
+ bool filebot_inpar = FALSE;
+ /* Whether the text at filebot is part of the current
+ * paragraph. */
+
+ /* We save these variables to be restored if the user
+ * unjustifies. */
+ filestruct *edittop_save = openfile->edittop;
+ filestruct *current_save = openfile->current;
+ size_t current_x_save = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ size_t totsize_save = openfile->totsize;
+#ifndef NANO_TINY
+ filestruct *mark_begin_save = openfile->mark_begin;
+ size_t mark_begin_x_save = openfile->mark_begin_x;
+#endif
+ bool modified_save = openfile->modified;
+
+ int kbinput;
+ bool meta_key, func_key, s_or_t, ran_func, finished;
+ const sc *s;
+
+ /* Move to the beginning of the current line, so that justifying at
+ * the end of the last line of the file, if that line isn't blank,
+ * will work the first time through. */
+ openfile->current_x = 0;
+
+ /* If we're justifying the entire file, start at the beginning. */
+ if (full_justify)
+ openfile->current = openfile->fileage;
+
+ while (TRUE) {
+ size_t i;
+ /* Generic loop variable. */
+ filestruct *curr_first_par_line;
+ /* The first line of the current paragraph. */
+ size_t quote_len;
+ /* Length of the initial quotation of the current
+ * paragraph. */
+ size_t indent_len;
+ /* Length of the initial indentation of the current
+ * paragraph. */
+ size_t par_len;
+ /* Number of lines in the current paragraph. */
+ ssize_t break_pos;
+ /* Where we will break lines. */
+ char *indent_string;
+ /* The first indentation that doesn't match the initial
+ * indentation of the current paragraph. This is put at the
+ * beginning of every line broken off the first justified
+ * line of the paragraph. Note that this works because a
+ * paragraph can only contain two indentations at most: the
+ * initial one, and a different one starting on a line after
+ * the first. See the comment at begpar() for more about
+ * when a line is part of a paragraph. */
+
+ /* Find the first line of the paragraph to be justified. That
+ * is the start of this paragraph if we're in one, or the start
+ * of the next otherwise. Save the quote length and paragraph
+ * length (number of lines). Don't refresh the screen yet,
+ * since we'll do that after we justify.
+ *
+ * If the search failed, we do one of two things. If we're
+ * justifying the whole file, and we've found at least one
+ * paragraph, it means that we should justify all the way to the
+ * last line of the file, so set the last line of the text to be
+ * justified to the last line of the file and break out of the
+ * loop. Otherwise, it means that there are no paragraph(s) to
+ * justify, so refresh the screen and get out. */
+ if (!find_paragraph(&quote_len, &par_len)) {
+ if (full_justify && first_par_line != NULL) {
+ last_par_line = openfile->filebot;
+ break;
+ } else {
+ edit_refresh_needed = TRUE;
+ return;
+ }
+ }
+
+ /* par_len will be one greater than the number of lines between
+ * current and filebot if filebot is the last line in the
+ * paragraph. Set filebot_inpar to TRUE if this is the case. */
+ filebot_inpar = (openfile->current->lineno + par_len ==
+ openfile->filebot->lineno + 1);
+
+ /* If we haven't already done it, move the original paragraph(s)
+ * to the justify buffer, splice a copy of the original
+ * paragraph(s) into the file in the same place, and set
+ * first_par_line to the first line of the copy. */
+ if (first_par_line == NULL) {
+ backup_lines(openfile->current, full_justify ?
+ openfile->filebot->lineno - openfile->current->lineno +
+ ((openfile->filebot->data[0] != '\0') ? 1 : 0) :
+ par_len);
+ first_par_line = openfile->current;
+ }
+
+ /* Set curr_first_par_line to the first line of the current
+ * paragraph. */
+ curr_first_par_line = openfile->current;
+
+ /* Initialize indent_string to a blank string. */
+ indent_string = mallocstrcpy(NULL, "");
+
+ /* Find the first indentation in the paragraph that doesn't
+ * match the indentation of the first line, and save it in
+ * indent_string. If all the indentations are the same, save
+ * the indentation of the first line in indent_string. */
+ {
+ const filestruct *indent_line = openfile->current;
+ bool past_first_line = FALSE;
+
+ for (i = 0; i < par_len; i++) {
+ indent_len = quote_len +
+ indent_length(indent_line->data + quote_len);
+
+ if (indent_len != strlen(indent_string)) {
+ indent_string = mallocstrncpy(indent_string,
+ indent_line->data, indent_len + 1);
+ indent_string[indent_len] = '\0';
+
+ if (past_first_line)
+ break;
+ }
+
+ if (indent_line == openfile->current)
+ past_first_line = TRUE;
+
+ indent_line = indent_line->next;
+ }
+ }
+
+ /* Now tack all the lines of the paragraph together, skipping
+ * the quoting and indentation on all lines after the first. */
+ for (i = 0; i < par_len - 1; i++) {
+ filestruct *next_line = openfile->current->next;
+ size_t line_len = strlen(openfile->current->data);
+ size_t next_line_len =
+ strlen(openfile->current->next->data);
+
+ indent_len = quote_len +
+ indent_length(openfile->current->next->data +
+ quote_len);
+
+ next_line_len -= indent_len;
+ openfile->totsize -= indent_len;
+
+ /* We're just about to tack the next line onto this one. If
+ * this line isn't empty, make sure it ends in a space. */
+ if (line_len > 0 &&
+ openfile->current->data[line_len - 1] != ' ') {
+ line_len++;
+ openfile->current->data =
+ charealloc(openfile->current->data,
+ line_len + 1);
+ openfile->current->data[line_len - 1] = ' ';
+ openfile->current->data[line_len] = '\0';
+ openfile->totsize++;
+ }
+
+ openfile->current->data =
+ charealloc(openfile->current->data, line_len +
+ next_line_len + 1);
+ strcat(openfile->current->data, next_line->data +
+ indent_len);
+
+ /* Don't destroy edittop or filebot! */
+ if (next_line == openfile->edittop)
+ openfile->edittop = openfile->current;
+ if (next_line == openfile->filebot)
+ openfile->filebot = openfile->current;
+
+#ifndef NANO_TINY
+ /* Adjust the mark coordinates to compensate for the change
+ * in the next line. */
+ if (openfile->mark_set && openfile->mark_begin ==
+ next_line) {
+ openfile->mark_begin = openfile->current;
+ openfile->mark_begin_x += line_len - indent_len;
+ }
+#endif
+
+ unlink_node(next_line);
+ delete_node(next_line);
+
+ /* If we've removed the next line, we need to go through
+ * this line again. */
+ i--;
+
+ par_len--;
+ openfile->totsize--;
+ }
+
+ /* Call justify_format() on the paragraph, which will remove
+ * excess spaces from it and change all blank characters to
+ * spaces. */
+ justify_format(openfile->current, quote_len +
+ indent_length(openfile->current->data + quote_len));
+
+ while (par_len > 0 && strlenpt(openfile->current->data) >
+ fill) {
+ size_t line_len = strlen(openfile->current->data);
+
+ indent_len = strlen(indent_string);
+
+ /* If this line is too long, try to wrap it to the next line
+ * to make it short enough. */
+ break_pos = break_line(openfile->current->data + indent_len,
+ fill - strnlenpt(openfile->current->data, indent_len)
+#ifndef DISABLE_HELP
+ , FALSE
+#endif
+ );
+
+ /* We can't break the line, or don't need to, so get out. */
+ if (break_pos == -1 || break_pos + indent_len == line_len)
+ break;
+
+ /* Move forward to the character after the indentation and
+ * just after the space. */
+ break_pos += indent_len + 1;
+
+ assert(break_pos <= line_len);
+
+ /* Make a new line, and copy the text after where we're
+ * going to break this line to the beginning of the new
+ * line. */
+ splice_node(openfile->current,
+ make_new_node(openfile->current),
+ openfile->current->next);
+
+ /* If this paragraph is non-quoted, and autoindent isn't
+ * turned on, set the indentation length to zero so that the
+ * indentation is treated as part of the line. */
+ if (quote_len == 0
+#ifndef NANO_TINY
+ && !ISSET(AUTOINDENT)
+#endif
+ )
+ indent_len = 0;
+
+ /* Copy the text after where we're going to break the
+ * current line to the next line. */
+ openfile->current->next->data = charalloc(indent_len + 1 +
+ line_len - break_pos);
+ strncpy(openfile->current->next->data, indent_string,
+ indent_len);
+ strcpy(openfile->current->next->data + indent_len,
+ openfile->current->data + break_pos);
+
+ par_len++;
+ openfile->totsize += indent_len + 1;
+
+#ifndef NANO_TINY
+ /* Adjust the mark coordinates to compensate for the change
+ * in the current line. */
+ if (openfile->mark_set && openfile->mark_begin ==
+ openfile->current && openfile->mark_begin_x >
+ break_pos) {
+ openfile->mark_begin = openfile->current->next;
+ openfile->mark_begin_x -= break_pos - indent_len;
+ }
+#endif
+
+ /* Break the current line. */
+ null_at(&openfile->current->data, break_pos);
+
+ /* If the current line is the last line of the file, move
+ * the last line of the file down to the next line. */
+ if (openfile->filebot == openfile->current)
+ openfile->filebot = openfile->filebot->next;
+
+ /* Go to the next line. */
+ par_len--;
+ openfile->current_y++;
+ openfile->current = openfile->current->next;
+ }
+
+ /* We're done breaking lines, so we don't need indent_string
+ * anymore. */
+ free(indent_string);
+
+ /* Go to the next line, if possible. If there is no next line,
+ * move to the end of the current line. */
+ if (openfile->current != openfile->filebot) {
+ openfile->current_y++;
+ openfile->current = openfile->current->next;
+ } else
+ openfile->current_x = strlen(openfile->current->data);
+
+ /* Renumber the lines of the now-justified current paragraph,
+ * since both find_paragraph() and edit_refresh() need the line
+ * numbers to be right. */
+ renumber(curr_first_par_line);
+
+ /* We've just finished justifying the paragraph. If we're not
+ * justifying the entire file, break out of the loop.
+ * Otherwise, continue the loop so that we justify all the
+ * paragraphs in the file. */
+ if (!full_justify)
+ break;
+ }
+
+ /* We are now done justifying the paragraph or the file, so clean
+ * up. current_y and totsize have been maintained above. If we
+ * actually justified something, set last_par_line to the new end of
+ * the paragraph. */
+ if (first_par_line != NULL)
+ last_par_line = openfile->current;
+
+ edit_refresh();
+
+#ifndef NANO_TINY
+ /* We're going to set jump_buf so that we return here after a
+ * SIGWINCH instead of to main(). Indicate this. */
+ jump_buf_main = FALSE;
+
+ /* Return here after a SIGWINCH. */
+ sigsetjmp(jump_buf, 1);
+#endif
+
+ statusbar(_("Can now UnJustify!"));
+
+ /* If constant cursor position display is on, make sure the current
+ * cursor position will be properly displayed on the statusbar. */
+ if (ISSET(CONST_UPDATE))
+ do_cursorpos(TRUE);
+
+ /* Display the shortcut list with UnJustify. */
+ shortcut_init(TRUE);
+ display_main_list();
+
+ /* Now get a keystroke and see if it's unjustify. If not, put back
+ * the keystroke and return. */
+ kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
+ &finished, FALSE);
+ s = get_shortcut(currmenu, &kbinput, &meta_key, &func_key);
+
+ if (s && s->scfunc == do_uncut_text) {
+ /* Splice the justify buffer back into the file, but only if we
+ * actually justified something. */
+ if (first_par_line != NULL) {
+ filestruct *top_save;
+
+ /* Partition the filestruct so that it contains only the
+ * text of the justified paragraph. */
+ filepart = partition_filestruct(first_par_line, 0,
+ last_par_line, filebot_inpar ?
+ strlen(last_par_line->data) : 0);
+
+ /* Remove the text of the justified paragraph, and
+ * replace it with the text in the justify buffer. */
+ free_filestruct(openfile->fileage);
+ openfile->fileage = jusbuffer;
+ openfile->filebot = jusbottom;
+
+ top_save = openfile->fileage;
+
+ /* Unpartition the filestruct so that it contains all the
+ * text again. Note that the justified paragraph has been
+ * replaced with the unjustified paragraph. */
+ unpartition_filestruct(&filepart);
+
+ /* Renumber starting with the beginning line of the old
+ * partition. */
+ renumber(top_save);
+
+ /* Restore the justify we just did (ungrateful user!). */
+ openfile->edittop = edittop_save;
+ openfile->current = current_save;
+ openfile->current_x = current_x_save;
+ openfile->placewewant = pww_save;
+ openfile->totsize = totsize_save;
+#ifndef NANO_TINY
+ if (openfile->mark_set) {
+ openfile->mark_begin = mark_begin_save;
+ openfile->mark_begin_x = mark_begin_x_save;
+ }
+#endif
+ openfile->modified = modified_save;
+
+ /* Clear the justify buffer. */
+ jusbuffer = NULL;
+
+ if (!openfile->modified)
+ titlebar(NULL);
+ edit_refresh_needed = TRUE;
+ }
+ } else {
+ unget_kbinput(kbinput, meta_key, func_key);
+
+ /* Blow away the text in the justify buffer. */
+ free_filestruct(jusbuffer);
+ jusbuffer = NULL;
+ }
+
+ blank_statusbar();
+
+ /* Display the shortcut list with UnCut. */
+ shortcut_init(FALSE);
+ display_main_list();
+}
+
+/* Justify the current paragraph. */
+void do_justify_void(void)
+{
+ do_justify(FALSE);
+}
+
+/* Justify the entire file. */
+void do_full_justify(void)
+{
+ do_justify(TRUE);
+}
+#endif /* !DISABLE_JUSTIFY */
+
+#ifndef DISABLE_SPELLER
+/* A word is misspelled in the file. Let the user replace it. We
+ * return FALSE if the user cancels. */
+bool do_int_spell_fix(const char *word)
+{
+ char *save_search, *save_replace;
+ size_t match_len, current_x_save = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ bool meta_key = FALSE, func_key = FALSE;
+ filestruct *edittop_save = openfile->edittop;
+ filestruct *current_save = openfile->current;
+ /* Save where we are. */
+ bool canceled = FALSE;
+ /* The return value. */
+ bool case_sens_set = ISSET(CASE_SENSITIVE);
+#ifndef NANO_TINY
+ bool backwards_search_set = ISSET(BACKWARDS_SEARCH);
+#endif
+#ifdef HAVE_REGEX_H
+ bool regexp_set = ISSET(USE_REGEXP);
+#endif
+#ifndef NANO_TINY
+ bool old_mark_set = openfile->mark_set;
+ bool added_magicline = FALSE;
+ /* Whether we added a magicline after filebot. */
+ bool right_side_up = FALSE;
+ /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
+ * FALSE if (current, current_x) is. */
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+#endif
+
+ /* Make sure spell-check is case sensitive. */
+ SET(CASE_SENSITIVE);
+
+#ifndef NANO_TINY
+ /* Make sure spell-check goes forward only. */
+ UNSET(BACKWARDS_SEARCH);
+#endif
+#ifdef HAVE_REGEX_H
+ /* Make sure spell-check doesn't use regular expressions. */
+ UNSET(USE_REGEXP);
+#endif
+
+ /* Save the current search/replace strings. */
+ search_init_globals();
+ save_search = last_search;
+ save_replace = last_replace;
+
+ /* Set the search/replace strings to the misspelled word. */
+ last_search = mallocstrcpy(NULL, word);
+ last_replace = mallocstrcpy(NULL, word);
+
+#ifndef NANO_TINY
+ if (old_mark_set) {
+ /* If the mark is on, partition the filestruct so that it
+ * contains only the marked text; if the NO_NEWLINES flag isn't
+ * set, keep track of whether the text will have a magicline
+ * added when we're done correcting misspelled words; and
+ * turn the mark off. */
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, &right_side_up);
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+ if (!ISSET(NO_NEWLINES))
+ added_magicline = (openfile->filebot->data[0] != '\0');
+ openfile->mark_set = FALSE;
+ }
+#endif
+
+ /* Start from the top of the file. */
+ openfile->edittop = openfile->fileage;
+ openfile->current = openfile->fileage;
+ openfile->current_x = (size_t)-1;
+ openfile->placewewant = 0;
+
+ /* Find the first whole occurrence of word. */
+ findnextstr_wrap_reset();
+ while (findnextstr(TRUE, FALSE, openfile->fileage, 0, word,
+ &match_len)) {
+ if (is_whole_word(openfile->current_x, openfile->current->data,
+ word)) {
+ size_t xpt = xplustabs();
+ char *exp_word = display_string(openfile->current->data,
+ xpt, strnlenpt(openfile->current->data,
+ openfile->current_x + match_len) - xpt, FALSE);
+
+ edit_refresh();
+
+ do_replace_highlight(TRUE, exp_word);
+
+ /* Allow all instances of the word to be corrected. */
+ canceled = (do_prompt(FALSE,
+#ifndef DISABLE_TABCOMP
+ TRUE,
+#endif
+ MSPELL, word,
+ &meta_key, &func_key,
+#ifndef NANO_TINY
+ NULL,
+#endif
+ edit_refresh, _("Edit a replacement")) == -1);
+
+ do_replace_highlight(FALSE, exp_word);
+
+ free(exp_word);
+
+ if (!canceled && strcmp(word, answer) != 0) {
+ openfile->current_x--;
+ do_replace_loop(TRUE, &canceled, openfile->current,
+ &openfile->current_x, word);
+ }
+
+ break;
+ }
+ }
+
+#ifndef NANO_TINY
+ if (old_mark_set) {
+ /* If the mark was on, the NO_NEWLINES flag isn't set, and we
+ * added a magicline, remove it now. */
+ if (!ISSET(NO_NEWLINES) && added_magicline)
+ remove_magicline();
+
+ /* Put the beginning and the end of the mark at the beginning
+ * and the end of the spell-checked text. */
+ if (openfile->fileage == openfile->filebot)
+ bot_x += top_x;
+ if (right_side_up) {
+ openfile->mark_begin_x = top_x;
+ current_x_save = bot_x;
+ } else {
+ current_x_save = top_x;
+ openfile->mark_begin_x = bot_x;
+ }
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again, and turn the mark back on. */
+ unpartition_filestruct(&filepart);
+ openfile->mark_set = TRUE;
+ }
+#endif
+
+ /* Restore the search/replace strings. */
+ free(last_search);
+ last_search = save_search;
+ free(last_replace);
+ last_replace = save_replace;
+
+ /* Restore where we were. */
+ openfile->edittop = edittop_save;
+ openfile->current = current_save;
+ openfile->current_x = current_x_save;
+ openfile->placewewant = pww_save;
+
+ /* Restore case sensitivity setting. */
+ if (!case_sens_set)
+ UNSET(CASE_SENSITIVE);
+
+#ifndef NANO_TINY
+ /* Restore search/replace direction. */
+ if (backwards_search_set)
+ SET(BACKWARDS_SEARCH);
+#endif
+#ifdef HAVE_REGEX_H
+ /* Restore regular expression usage setting. */
+ if (regexp_set)
+ SET(USE_REGEXP);
+#endif
+
+ return !canceled;
+}
+
+/* Internal (integrated) spell checking using the spell program,
+ * filtered through the sort and uniq programs. Return NULL for normal
+ * termination, and the error string otherwise. */
+const char *do_int_speller(const char *tempfile_name)
+{
+ char *read_buff, *read_buff_ptr, *read_buff_word;
+ size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
+ int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
+ pid_t pid_spell, pid_sort, pid_uniq;
+ int spell_status, sort_status, uniq_status;
+
+ /* Create all three pipes up front. */
+ if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
+ pipe(uniq_fd) == -1)
+ return _("Could not create pipe");
+
+ statusbar(_("Creating misspelled word list, please wait..."));
+
+ /* A new process to run spell in. */
+ if ((pid_spell = fork()) == 0) {
+ /* Child continues (i.e. future spell process). */
+ close(spell_fd[0]);
+
+ /* Replace the standard input with the temp file. */
+ if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
+ goto close_pipes_and_exit;
+
+ if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO)
+ goto close_pipes_and_exit;
+
+ close(tempfile_fd);
+
+ /* Send spell's standard output to the pipe. */
+ if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
+ goto close_pipes_and_exit;
+
+ close(spell_fd[1]);
+
+ /* Start the spell program; we are using $PATH. */
+ execlp("spell", "spell", NULL);
+
+ /* This should not be reached if spell is found. */
+ exit(1);
+ }
+
+ /* Parent continues here. */
+ close(spell_fd[1]);
+
+ /* A new process to run sort in. */
+ if ((pid_sort = fork()) == 0) {
+ /* Child continues (i.e. future spell process). Replace the
+ * standard input with the standard output of the old pipe. */
+ if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
+ goto close_pipes_and_exit;
+
+ close(spell_fd[0]);
+
+ /* Send sort's standard output to the new pipe. */
+ if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
+ goto close_pipes_and_exit;
+
+ close(sort_fd[1]);
+
+ /* Start the sort program. Use -f to remove mixed case. If
+ * this isn't portable, let me know. */
+ execlp("sort", "sort", "-f", NULL);
+
+ /* This should not be reached if sort is found. */
+ exit(1);
+ }
+
+ close(spell_fd[0]);
+ close(sort_fd[1]);
+
+ /* A new process to run uniq in. */
+ if ((pid_uniq = fork()) == 0) {
+ /* Child continues (i.e. future uniq process). Replace the
+ * standard input with the standard output of the old pipe. */
+ if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
+ goto close_pipes_and_exit;
+
+ close(sort_fd[0]);
+
+ /* Send uniq's standard output to the new pipe. */
+ if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
+ goto close_pipes_and_exit;
+
+ close(uniq_fd[1]);
+
+ /* Start the uniq program; we are using PATH. */
+ execlp("uniq", "uniq", NULL);
+
+ /* This should not be reached if uniq is found. */
+ exit(1);
+ }
+
+ close(sort_fd[0]);
+ close(uniq_fd[1]);
+
+ /* The child process was not forked successfully. */
+ if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
+ close(uniq_fd[0]);
+ return _("Could not fork");
+ }
+
+ /* Get the system pipe buffer size. */
+ if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
+ close(uniq_fd[0]);
+ return _("Could not get size of pipe buffer");
+ }
+
+ /* Read in the returned spelling errors. */
+ read_buff_read = 0;
+ read_buff_size = pipe_buff_size + 1;
+ read_buff = read_buff_ptr = charalloc(read_buff_size);
+
+ while ((bytesread = read(uniq_fd[0], read_buff_ptr,
+ pipe_buff_size)) > 0) {
+ read_buff_read += bytesread;
+ read_buff_size += pipe_buff_size;
+ read_buff = read_buff_ptr = charealloc(read_buff,
+ read_buff_size);
+ read_buff_ptr += read_buff_read;
+ }
+
+ *read_buff_ptr = '\0';
+ close(uniq_fd[0]);
+
+ /* Process the spelling errors. */
+ read_buff_word = read_buff_ptr = read_buff;
+
+ while (*read_buff_ptr != '\0') {
+ if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
+ *read_buff_ptr = '\0';
+ if (read_buff_word != read_buff_ptr) {
+ if (!do_int_spell_fix(read_buff_word)) {
+ read_buff_word = read_buff_ptr;
+ break;
+ }
+ }
+ read_buff_word = read_buff_ptr + 1;
+ }
+ read_buff_ptr++;
+ }
+
+ /* Special case: the last word doesn't end with '\r' or '\n'. */
+ if (read_buff_word != read_buff_ptr)
+ do_int_spell_fix(read_buff_word);
+
+ free(read_buff);
+ search_replace_abort();
+ edit_refresh_needed = TRUE;
+
+ /* Process the end of the spell process. */
+ waitpid(pid_spell, &spell_status, 0);
+ waitpid(pid_sort, &sort_status, 0);
+ waitpid(pid_uniq, &uniq_status, 0);
+
+ if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
+ return _("Error invoking \"spell\"");
+
+ if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
+ return _("Error invoking \"sort -f\"");
+
+ if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
+ return _("Error invoking \"uniq\"");
+
+ /* Otherwise... */
+ return NULL;
+
+ close_pipes_and_exit:
+ /* Don't leak any handles. */
+ close(tempfile_fd);
+ close(spell_fd[0]);
+ close(spell_fd[1]);
+ close(sort_fd[0]);
+ close(sort_fd[1]);
+ close(uniq_fd[0]);
+ close(uniq_fd[1]);
+ exit(1);
+}
+
+/* External (alternate) spell checking. Return NULL for normal
+ * termination, and the error string otherwise. */
+const char *do_alt_speller(char *tempfile_name)
+{
+ int alt_spell_status;
+ size_t current_x_save = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ ssize_t current_y_save = openfile->current_y;
+ ssize_t lineno_save = openfile->current->lineno;
+ pid_t pid_spell;
+ char *ptr;
+ static int arglen = 3;
+ static char **spellargs = NULL;
+#ifndef NANO_TINY
+ bool old_mark_set = openfile->mark_set;
+ bool added_magicline = FALSE;
+ /* Whether we added a magicline after filebot. */
+ bool right_side_up = FALSE;
+ /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
+ * FALSE if (current, current_x) is. */
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+ ssize_t mb_lineno_save = 0;
+ /* We're going to close the current file, and open the output of
+ * the alternate spell command. The line that mark_begin points
+ * to will be freed, so we save the line number and restore it
+ * afterwards. */
+ size_t totsize_save = openfile->totsize;
+ /* Our saved value of totsize, used when we spell-check a marked
+ * selection. */
+
+ if (old_mark_set) {
+ /* If the mark is on, save the number of the line it starts on,
+ * and then turn the mark off. */
+ mb_lineno_save = openfile->mark_begin->lineno;
+ openfile->mark_set = FALSE;
+ }
+#endif
+
+ if (openfile->totsize == 0) {
+ statusbar(_("Finished checking spelling"));
+ return NULL;
+ }
+
+ endwin();
+
+ /* Set up an argument list to pass execvp(). */
+ if (spellargs == NULL) {
+ spellargs = (char **)nmalloc(arglen * sizeof(char *));
+
+ spellargs[0] = strtok(alt_speller, " ");
+ while ((ptr = strtok(NULL, " ")) != NULL) {
+ arglen++;
+ spellargs = (char **)nrealloc(spellargs, arglen *
+ sizeof(char *));
+ spellargs[arglen - 3] = ptr;
+ }
+ spellargs[arglen - 1] = NULL;
+ }
+ spellargs[arglen - 2] = tempfile_name;
+
+ /* Start a new process for the alternate speller. */
+ if ((pid_spell = fork()) == 0) {
+ /* Start alternate spell program; we are using $PATH. */
+ execvp(spellargs[0], spellargs);
+
+ /* Should not be reached, if alternate speller is found!!! */
+ exit(1);
+ }
+
+ /* If we couldn't fork, get out. */
+ if (pid_spell < 0)
+ return _("Could not fork");
+
+#ifndef NANO_TINY
+ /* Don't handle a pending SIGWINCH until the alternate spell checker
+ * is finished and we've loaded the spell-checked file back in. */
+ allow_pending_sigwinch(FALSE);
+#endif
+
+ /* Wait for the alternate spell checker to finish. */
+ wait(&alt_spell_status);
+
+ /* Reenter curses mode. */
+ doupdate();
+
+ /* Restore the terminal to its previous state. */
+ terminal_init();
+
+ /* Turn the cursor back on for sure. */
+ curs_set(1);
+
+ /* The screen might have been resized. If it has, reinitialize all
+ * the windows based on the new screen dimensions. */
+ window_init();
+
+ if (!WIFEXITED(alt_spell_status) ||
+ WEXITSTATUS(alt_spell_status) != 0) {
+ char *alt_spell_error;
+ char *invoke_error = _("Error invoking \"%s\"");
+
+#ifndef NANO_TINY
+ /* Turn the mark back on if it was on before. */
+ openfile->mark_set = old_mark_set;
+#endif
+
+ alt_spell_error =
+ charalloc(strlen(invoke_error) +
+ strlen(alt_speller) + 1);
+ sprintf(alt_spell_error, invoke_error, alt_speller);
+ return alt_spell_error;
+ }
+
+#ifndef NANO_TINY
+ if (old_mark_set) {
+ /* If the mark is on, partition the filestruct so that it
+ * contains only the marked text; if the NO_NEWLINES flag isn't
+ * set, keep track of whether the text will have a magicline
+ * added when we're done correcting misspelled words; and
+ * turn the mark off. */
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, &right_side_up);
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+ if (!ISSET(NO_NEWLINES))
+ added_magicline = (openfile->filebot->data[0] != '\0');
+
+ /* Get the number of characters in the marked text, and subtract
+ * it from the saved value of totsize. */
+ totsize_save -= get_totsize(top, bot);
+ }
+#endif
+
+ /* Replace the text of the current buffer with the spell-checked
+ * text. */
+ replace_buffer(tempfile_name);
+
+#ifndef NANO_TINY
+ if (old_mark_set) {
+ filestruct *top_save = openfile->fileage;
+
+ /* If the mark was on, the NO_NEWLINES flag isn't set, and we
+ * added a magicline, remove it now. */
+ if (!ISSET(NO_NEWLINES) && added_magicline)
+ remove_magicline();
+
+ /* Put the beginning and the end of the mark at the beginning
+ * and the end of the spell-checked text. */
+ if (openfile->fileage == openfile->filebot)
+ bot_x += top_x;
+ if (right_side_up) {
+ openfile->mark_begin_x = top_x;
+ current_x_save = bot_x;
+ } else {
+ current_x_save = top_x;
+ openfile->mark_begin_x = bot_x;
+ }
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again. Note that we've replaced the marked text originally
+ * in the partition with the spell-checked marked text in the
+ * temp file. */
+ unpartition_filestruct(&filepart);
+
+ /* Renumber starting with the beginning line of the old
+ * partition. Also add the number of characters in the
+ * spell-checked marked text to the saved value of totsize, and
+ * then make that saved value the actual value. */
+ renumber(top_save);
+ totsize_save += openfile->totsize;
+ openfile->totsize = totsize_save;
+
+ /* Assign mark_begin to the line where the mark began before. */
+ do_gotopos(mb_lineno_save, openfile->mark_begin_x,
+ current_y_save, 0);
+ openfile->mark_begin = openfile->current;
+
+ /* Assign mark_begin_x to the location in mark_begin where the
+ * mark began before, adjusted for any shortening of the
+ * line. */
+ openfile->mark_begin_x = openfile->current_x;
+
+ /* Turn the mark back on. */
+ openfile->mark_set = TRUE;
+ }
+#endif
+
+ /* Go back to the old position, and mark the file as modified. */
+ do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
+ set_modified();
+
+#ifndef NANO_TINY
+ /* Handle a pending SIGWINCH again. */
+ allow_pending_sigwinch(TRUE);
+#endif
+
+ return NULL;
+}
+
+/* Spell check the current file. If an alternate spell checker is
+ * specified, use it. Otherwise, use the internal spell checker. */
+void do_spell(void)
+{
+ bool status;
+ FILE *temp_file;
+ char *temp = safe_tempfile(&temp_file);
+ const char *spell_msg;
+
+ if (ISSET(RESTRICTED)) {
+ nano_disabled_msg();
+ return;
+ }
+
+ if (temp == NULL) {
+ statusbar(_("Error writing temp file: %s"), strerror(errno));
+ return;
+ }
+
+ status =
+#ifndef NANO_TINY
+ openfile->mark_set ? write_marked_file(temp, temp_file, TRUE,
+ OVERWRITE) :
+#endif
+ write_file(temp, temp_file, TRUE, OVERWRITE, FALSE);
+
+ if (!status) {
+ statusbar(_("Error writing temp file: %s"), strerror(errno));
+ free(temp);
+ return;
+ }
+
+ spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
+ do_int_speller(temp);
+ unlink(temp);
+ free(temp);
+
+ currmenu = MMAIN;
+
+ /* If the spell-checker printed any error messages onscreen, make
+ * sure that they're cleared off. */
+ total_refresh();
+
+ if (spell_msg != NULL) {
+ if (errno == 0)
+ /* Don't display an error message of "Success". */
+ statusbar(_("Spell checking failed: %s"), spell_msg);
+ else
+ statusbar(_("Spell checking failed: %s: %s"), spell_msg,
+ strerror(errno));
+ } else
+ statusbar(_("Finished checking spelling"));
+}
+#endif /* !DISABLE_SPELLER */
+
+#ifndef NANO_TINY
+/* Our own version of "wc". Note that its character counts are in
+ * multibyte characters instead of single-byte characters. */
+void do_wordlinechar_count(void)
+{
+ size_t words = 0, chars = 0;
+ ssize_t nlines = 0;
+ size_t current_x_save = openfile->current_x;
+ size_t pww_save = openfile->placewewant;
+ filestruct *current_save = openfile->current;
+ bool old_mark_set = openfile->mark_set;
+ filestruct *top, *bot;
+ size_t top_x, bot_x;
+
+ if (old_mark_set) {
+ /* If the mark is on, partition the filestruct so that it
+ * contains only the marked text, and turn the mark off. */
+ mark_order((const filestruct **)&top, &top_x,
+ (const filestruct **)&bot, &bot_x, NULL);
+ filepart = partition_filestruct(top, top_x, bot, bot_x);
+ openfile->mark_set = FALSE;
+ }
+
+ /* Start at the top of the file. */
+ openfile->current = openfile->fileage;
+ openfile->current_x = 0;
+ openfile->placewewant = 0;
+
+ /* Keep moving to the next word (counting punctuation characters as
+ * part of a word, as "wc -w" does), without updating the screen,
+ * until we reach the end of the file, incrementing the total word
+ * count whenever we're on a word just before moving. */
+ while (openfile->current != openfile->filebot ||
+ openfile->current->data[openfile->current_x] != '\0') {
+ if (do_next_word(TRUE, FALSE))
+ words++;
+ }
+
+ /* Get the total line and character counts, as "wc -l" and "wc -c"
+ * do, but get the latter in multibyte characters. */
+ if (old_mark_set) {
+ nlines = openfile->filebot->lineno -
+ openfile->fileage->lineno + 1;
+ chars = get_totsize(openfile->fileage, openfile->filebot);
+
+ /* Unpartition the filestruct so that it contains all the text
+ * again, and turn the mark back on. */
+ unpartition_filestruct(&filepart);
+ openfile->mark_set = TRUE;
+ } else {
+ nlines = openfile->filebot->lineno;
+ chars = openfile->totsize;
+ }
+
+ /* Restore where we were. */
+ openfile->current = current_save;
+ openfile->current_x = current_x_save;
+ openfile->placewewant = pww_save;
+
+ /* Display the total word, line, and character counts on the
+ * statusbar. */
+ statusbar(_("%sWords: %lu Lines: %ld Chars: %lu"), old_mark_set ?
+ _("In Selection: ") : "", (unsigned long)words, (long)nlines,
+ (unsigned long)chars);
+}
+#endif /* !NANO_TINY */
+
+/* Get verbatim input. */
+void do_verbatim_input(void)
+{
+ int *kbinput;
+ size_t kbinput_len, i;
+ char *output;
+
+ /* TRANSLATORS: This is displayed when the next keystroke will be
+ * inserted verbatim. */
+ statusbar(_("Verbatim Input"));
+
+ /* Read in all the verbatim characters. */
+ kbinput = get_verbatim_kbinput(edit, &kbinput_len);
+
+ /* If constant cursor position display is on, make sure the current
+ * cursor position will be properly displayed on the statusbar.
+ * Otherwise, blank the statusbar. */
+ if (ISSET(CONST_UPDATE))
+ do_cursorpos(TRUE);
+ else {
+ blank_statusbar();
+ wnoutrefresh(bottomwin);
+ }
+
+ /* Display all the verbatim characters at once, not filtering out
+ * control characters. */
+ output = charalloc(kbinput_len + 1);
+
+ for (i = 0; i < kbinput_len; i++)
+ output[i] = (char)kbinput[i];
+ output[i] = '\0';
+
+ free(kbinput);
+
+ do_output(output, kbinput_len, TRUE);
+
+ free(output);
+}
+
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..ff13a41
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,674 @@
+/* $Id: utils.c 4453 2009-12-02 03:36:22Z astyanax $ */
+/**************************************************************************
+ * utils.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <errno.h>
+
+/* Return the number of decimal digits in n. */
+int digits(size_t n)
+{
+ int i;
+
+ if (n == 0)
+ i = 1;
+ else {
+ for (i = 0; n != 0; n /= 10, i++)
+ ;
+ }
+
+ return i;
+}
+
+/* Return the user's home directory. We use $HOME, and if that fails,
+ * we fall back on the home directory of the effective user ID. */
+void get_homedir(void)
+{
+ if (homedir == NULL) {
+ const char *homenv = getenv("HOME");
+
+ if (homenv == NULL) {
+ const struct passwd *userage = getpwuid(geteuid());
+
+ if (userage != NULL)
+ homenv = userage->pw_dir;
+ }
+ homedir = mallocstrcpy(NULL, homenv);
+ }
+}
+
+/* Read a ssize_t from str, and store it in *val (if val is not NULL).
+ * On error, we return FALSE and don't change *val. Otherwise, we
+ * return TRUE. */
+bool parse_num(const char *str, ssize_t *val)
+{
+ char *first_error;
+ ssize_t j;
+
+ assert(str != NULL);
+
+ j = (ssize_t)strtol(str, &first_error, 10);
+
+ if (errno == ERANGE || *str == '\0' || *first_error != '\0')
+ return FALSE;
+
+ if (val != NULL)
+ *val = j;
+
+ return TRUE;
+}
+
+/* Read two ssize_t's, separated by a comma, from str, and store them in
+ * *line and *column (if they're not both NULL). Return FALSE on error,
+ * or TRUE otherwise. */
+bool parse_line_column(const char *str, ssize_t *line, ssize_t *column)
+{
+ bool retval = TRUE;
+ const char *comma;
+
+ assert(str != NULL);
+
+ comma = strchr(str, ',');
+
+ if (comma != NULL && column != NULL) {
+ if (!parse_num(comma + 1, column))
+ retval = FALSE;
+ }
+
+ if (line != NULL) {
+ if (comma != NULL) {
+ char *str_line = mallocstrncpy(NULL, str, comma - str + 1);
+ str_line[comma - str] = '\0';
+
+ if (str_line[0] != '\0' && !parse_num(str_line, line))
+ retval = FALSE;
+
+ free(str_line);
+ } else if (!parse_num(str, line))
+ retval = FALSE;
+ }
+
+ return retval;
+}
+
+/* Fix the memory allocation for a string. */
+void align(char **str)
+{
+ assert(str != NULL);
+
+ if (*str != NULL)
+ *str = charealloc(*str, strlen(*str) + 1);
+}
+
+/* Null a string at a certain index and align it. */
+void null_at(char **data, size_t index)
+{
+ assert(data != NULL);
+
+ *data = charealloc(*data, index + 1);
+ (*data)[index] = '\0';
+}
+
+/* For non-null-terminated lines. A line, by definition, shouldn't
+ * normally have newlines in it, so encode its nulls as newlines. */
+void unsunder(char *str, size_t true_len)
+{
+ assert(str != NULL);
+
+ for (; true_len > 0; true_len--, str++) {
+ if (*str == '\0')
+ *str = '\n';
+ }
+}
+
+/* For non-null-terminated lines. A line, by definition, shouldn't
+ * normally have newlines in it, so decode its newlines as nulls. */
+void sunder(char *str)
+{
+ assert(str != NULL);
+
+ for (; *str != '\0'; str++) {
+ if (*str == '\n')
+ *str = '\0';
+ }
+}
+
+/* These functions, ngetline() (originally getline()) and ngetdelim()
+ * (originally getdelim()), were adapted from GNU mailutils 0.5
+ * (mailbox/getline.c). Here is the notice from that file, after
+ * converting to the GPL via LGPL clause 3, and with the Free Software
+ * Foundation's address and the copyright years updated:
+ *
+ * GNU Mailutils -- a suite of utilities for electronic mail
+ * Copyright (C) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007
+ * Free Software Foundation, Inc.
+ *
+ * This library 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301, USA. */
+
+#ifdef ENABLE_NANORC
+
+#ifndef HAVE_GETDELIM
+/* This function is equivalent to getdelim(). */
+ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream)
+{
+ size_t indx = 0;
+ int c;
+
+ /* Sanity checks. */
+ if (lineptr == NULL || n == NULL || stream == NULL ||
+ fileno(stream) == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Allocate the line the first time. */
+ if (*lineptr == NULL) {
+ *n = MAX_BUF_SIZE;
+ *lineptr = charalloc(*n);
+ }
+
+ while ((c = getc(stream)) != EOF) {
+ /* Check if more memory is needed. */
+ if (indx >= *n) {
+ *n += MAX_BUF_SIZE;
+ *lineptr = charealloc(*lineptr, *n);
+ }
+
+ /* Put the result in the line. */
+ (*lineptr)[indx++] = (char)c;
+
+ /* Bail out. */
+ if (c == delim)
+ break;
+ }
+
+ /* Make room for the null character. */
+ if (indx >= *n) {
+ *n += MAX_BUF_SIZE;
+ *lineptr = charealloc(*lineptr, *n);
+ }
+
+ /* Null-terminate the buffer. */
+ null_at(lineptr, indx++);
+ *n = indx;
+
+ /* The last line may not have the delimiter. We have to return what
+ * we got, and the error will be seen on the next iteration. */
+ return (c == EOF && (indx - 1) == 0) ? -1 : indx - 1;
+}
+#endif
+
+#ifndef HAVE_GETLINE
+/* This function is equivalent to getline(). */
+ssize_t ngetline(char **lineptr, size_t *n, FILE *stream)
+{
+ return getdelim(lineptr, n, '\n', stream);
+}
+#endif
+#endif /* ENABLE_NANORC */
+
+#ifdef HAVE_REGEX_H
+/* Do the compiled regex in preg and the regex in string match the
+ * beginning or end of a line? */
+bool regexp_bol_or_eol(const regex_t *preg, const char *string)
+{
+ return (regexec(preg, string, 0, NULL, 0) == 0 &&
+ regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) ==
+ REG_NOMATCH);
+}
+
+/* Fix the regex if we're on platforms which requires an adjustment
+ * from GNU-style to BSD-style word boundaries. */
+const char *fixbounds(const char *r) {
+#ifndef GNU_WORDBOUNDS
+ int i, j = 0;
+ char *r2 = charalloc(strlen(r) * 5);
+ char *r3;
+
+#ifdef DEBUG
+ fprintf(stderr, "fixbounds(): Start string = \"%s\"\n", r);
+#endif
+
+ for (i = 0; i < strlen(r); i++) {
+ if (r[i] != '\0' && r[i] == '\\' && (r[i+1] == '>' || r[i+1] == '<')) {
+ strcpy(&r2[j], "[[:");
+ r2[j+3] = r[i+1];
+ strcpy(&r2[j+4], ":]]");
+ i++;
+ j += 6;
+ } else
+ r2[j] = r[i];
+ j++;
+ }
+ r2[j] = '\0';
+ r3 = mallocstrcpy(NULL, r2);
+ free(r2);
+#ifdef DEBUG
+ fprintf(stderr, "fixbounds(): Ending string = \"%s\"\n", r3);
+#endif
+ return (const char *) r3;
+#endif
+
+ return r;
+}
+
+#endif
+
+#ifndef DISABLE_SPELLER
+/* Is the word starting at position pos in buf a whole word? */
+bool is_whole_word(size_t pos, const char *buf, const char *word)
+{
+ char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max());
+ size_t word_end = pos + strlen(word);
+ bool retval;
+
+ assert(buf != NULL && pos <= strlen(buf) && word != NULL);
+
+ parse_mbchar(buf + move_mbleft(buf, pos), p, NULL);
+ parse_mbchar(buf + word_end, r, NULL);
+
+ /* If we're at the beginning of the line or the character before the
+ * word isn't a non-punctuation "word" character, and if we're at
+ * the end of the line or the character after the word isn't a
+ * non-punctuation "word" character, we have a whole word. */
+ retval = (pos == 0 || !is_word_mbchar(p, FALSE)) &&
+ (word_end == strlen(buf) || !is_word_mbchar(r, FALSE));
+
+ free(p);
+ free(r);
+
+ return retval;
+}
+#endif /* !DISABLE_SPELLER */
+
+/* If we are searching backwards, we will find the last match that
+ * starts no later than start. Otherwise we find the first match
+ * starting no earlier than start. If we are doing a regexp search, we
+ * fill in the global variable regmatches with at most 9 subexpression
+ * matches. Also, all .rm_so elements are relative to the start of the
+ * whole match, so regmatches[0].rm_so == 0. */
+const char *strstrwrapper(const char *haystack, const char *needle,
+ const char *start)
+{
+ /* start can be 1 character before the start or after the end of the
+ * line. In either case, we just say no match was found. */
+ if ((start > haystack && *(start - 1) == '\0') || start < haystack)
+ return NULL;
+
+ assert(haystack != NULL && needle != NULL && start != NULL);
+
+#ifdef HAVE_REGEX_H
+ if (ISSET(USE_REGEXP)) {
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH)) {
+ if (regexec(&search_regexp, haystack, 1, regmatches,
+ 0) == 0 && haystack + regmatches[0].rm_so <= start) {
+ const char *retval = haystack + regmatches[0].rm_so;
+
+ /* Search forward until there are no more matches. */
+ while (regexec(&search_regexp, retval + 1, 1,
+ regmatches, REG_NOTBOL) == 0 &&
+ retval + regmatches[0].rm_so + 1 <= start)
+ retval += regmatches[0].rm_so + 1;
+ /* Finally, put the subexpression matches in global
+ * variable regmatches. The REG_NOTBOL flag doesn't
+ * matter now. */
+ regexec(&search_regexp, retval, 10, regmatches, 0);
+ return retval;
+ }
+ } else
+#endif /* !NANO_TINY */
+ if (regexec(&search_regexp, start, 10, regmatches,
+ (start > haystack) ? REG_NOTBOL : 0) == 0) {
+ const char *retval = start + regmatches[0].rm_so;
+
+ regexec(&search_regexp, retval, 10, regmatches, 0);
+ return retval;
+ }
+ return NULL;
+ }
+#endif /* HAVE_REGEX_H */
+#if !defined(NANO_TINY) || !defined(DISABLE_SPELLER)
+ if (ISSET(CASE_SENSITIVE)) {
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH))
+ return revstrstr(haystack, needle, start);
+ else
+#endif
+ return strstr(start, needle);
+ }
+#endif /* !DISABLE_SPELLER || !NANO_TINY */
+#ifndef NANO_TINY
+ else if (ISSET(BACKWARDS_SEARCH))
+ return mbrevstrcasestr(haystack, needle, start);
+#endif
+ return mbstrcasestr(start, needle);
+}
+
+/* This is a wrapper for the perror() function. The wrapper temporarily
+ * leaves curses mode, calls perror() (which writes to stderr), and then
+ * reenters curses mode, updating the screen in the process. Note that
+ * nperror() causes the window to flicker once. */
+void nperror(const char *s)
+{
+ endwin();
+ perror(s);
+ doupdate();
+}
+
+/* This is a wrapper for the malloc() function that properly handles
+ * things when we run out of memory. Thanks, BG, many people have been
+ * asking for this... */
+void *nmalloc(size_t howmuch)
+{
+ void *r = malloc(howmuch);
+
+ if (r == NULL && howmuch != 0)
+ die(_("nano is out of memory!"));
+
+ return r;
+}
+
+/* This is a wrapper for the realloc() function that properly handles
+ * things when we run out of memory. */
+void *nrealloc(void *ptr, size_t howmuch)
+{
+ void *r = realloc(ptr, howmuch);
+
+ if (r == NULL && howmuch != 0)
+ die(_("nano is out of memory!"));
+
+ return r;
+}
+
+/* Copy the first n characters of one malloc()ed string to another
+ * pointer. Should be used as: "dest = mallocstrncpy(dest, src,
+ * n);". */
+char *mallocstrncpy(char *dest, const char *src, size_t n)
+{
+ if (src == NULL)
+ src = "";
+
+ if (src != dest)
+ free(dest);
+
+ dest = charalloc(n);
+ strncpy(dest, src, n);
+
+ return dest;
+}
+
+/* Copy one malloc()ed string to another pointer. Should be used as:
+ * "dest = mallocstrcpy(dest, src);". */
+char *mallocstrcpy(char *dest, const char *src)
+{
+ return mallocstrncpy(dest, src, (src == NULL) ? 1 :
+ strlen(src) + 1);
+}
+
+/* Free the malloc()ed string at dest and return the malloc()ed string
+ * at src. Should be used as: "answer = mallocstrassn(answer,
+ * real_dir_from_tilde(answer));". */
+char *mallocstrassn(char *dest, char *src)
+{
+ free(dest);
+ return src;
+}
+
+/* nano scrolls horizontally within a line in chunks. Return the column
+ * number of the first character displayed in the edit window when the
+ * cursor is at the given column. Note that (0 <= column -
+ * get_page_start(column) < COLS). */
+size_t get_page_start(size_t column)
+{
+ if (column == 0 || column < COLS - 1)
+ return 0;
+ else if (COLS > 8)
+ return column - 7 - (column - 7) % (COLS - 8);
+ else
+ return column - (COLS - 2);
+}
+
+/* Return the placewewant associated with current_x, i.e. the zero-based
+ * column position of the cursor. The value will be no smaller than
+ * current_x. */
+size_t xplustabs(void)
+{
+ return strnlenpt(openfile->current->data, openfile->current_x);
+}
+
+/* Return the index in s of the character displayed at the given column,
+ * i.e. the largest value such that strnlenpt(s, actual_x(s, column)) <=
+ * column. */
+size_t actual_x(const char *s, size_t column)
+{
+ size_t i = 0;
+ /* The position in s, returned. */
+ size_t len = 0;
+ /* The screen display width to s[i]. */
+
+ assert(s != NULL);
+
+ while (*s != '\0') {
+ int s_len = parse_mbchar(s, NULL, &len);
+
+ if (len > column)
+ break;
+
+ i += s_len;
+ s += s_len;
+ }
+
+ return i;
+}
+
+/* A strnlen() with tabs and multicolumn characters factored in, similar
+ * to xplustabs(). How many columns wide are the first maxlen characters
+ * of s? */
+size_t strnlenpt(const char *s, size_t maxlen)
+{
+ size_t len = 0;
+ /* The screen display width to s[i]. */
+
+ if (maxlen == 0)
+ return 0;
+
+ assert(s != NULL);
+
+ while (*s != '\0') {
+ int s_len = parse_mbchar(s, NULL, &len);
+
+ s += s_len;
+
+ if (maxlen <= s_len)
+ break;
+
+ maxlen -= s_len;
+ }
+
+ return len;
+}
+
+/* A strlen() with tabs and multicolumn characters factored in, similar
+ * to xplustabs(). How many columns wide is s? */
+size_t strlenpt(const char *s)
+{
+ return strnlenpt(s, (size_t)-1);
+}
+
+/* Append a new magicline to filebot. */
+void new_magicline(void)
+{
+ openfile->filebot->next = (filestruct *)nmalloc(sizeof(filestruct));
+ openfile->filebot->next->data = mallocstrcpy(NULL, "");
+ openfile->filebot->next->prev = openfile->filebot;
+ openfile->filebot->next->next = NULL;
+ openfile->filebot->next->lineno = openfile->filebot->lineno + 1;
+#ifdef ENABLE_COLOR
+ openfile->filebot->next->multidata = NULL;
+#endif
+ openfile->filebot = openfile->filebot->next;
+ openfile->totsize++;
+}
+
+#ifndef NANO_TINY
+/* Remove the magicline from filebot, if there is one and it isn't the
+ * only line in the file. Assume that edittop and current are not at
+ * filebot. */
+void remove_magicline(void)
+{
+ if (openfile->filebot->data[0] == '\0' &&
+ openfile->filebot != openfile->fileage) {
+ assert(openfile->filebot != openfile->edittop && openfile->filebot != openfile->current);
+
+ openfile->filebot = openfile->filebot->prev;
+ free_filestruct(openfile->filebot->next);
+ openfile->filebot->next = NULL;
+ openfile->totsize--;
+ }
+}
+
+/* Set top_x and bot_x to the top and bottom x-coordinates of the mark,
+ * respectively, based on the locations of top and bot. If
+ * right_side_up isn't NULL, set it to TRUE if the mark begins with
+ * (mark_begin, mark_begin_x) and ends with (current, current_x), or
+ * FALSE otherwise. */
+void mark_order(const filestruct **top, size_t *top_x, const filestruct
+ **bot, size_t *bot_x, bool *right_side_up)
+{
+ assert(top != NULL && top_x != NULL && bot != NULL && bot_x != NULL);
+
+ if ((openfile->current->lineno == openfile->mark_begin->lineno &&
+ openfile->current_x > openfile->mark_begin_x) ||
+ openfile->current->lineno > openfile->mark_begin->lineno) {
+ *top = openfile->mark_begin;
+ *top_x = openfile->mark_begin_x;
+ *bot = openfile->current;
+ *bot_x = openfile->current_x;
+ if (right_side_up != NULL)
+ *right_side_up = TRUE;
+ } else {
+ *bot = openfile->mark_begin;
+ *bot_x = openfile->mark_begin_x;
+ *top = openfile->current;
+ *top_x = openfile->current_x;
+ if (right_side_up != NULL)
+ *right_side_up = FALSE;
+ }
+}
+#endif
+
+/* Calculate the number of characters between begin and end, and return
+ * it. */
+size_t get_totsize(const filestruct *begin, const filestruct *end)
+{
+ size_t totsize = 0;
+ const filestruct *f;
+
+ /* Go through the lines from begin to end->prev, if we can. */
+ for (f = begin; f != end && f != NULL; f = f->next) {
+ /* Count the number of characters on this line. */
+ totsize += mbstrlen(f->data);
+
+ /* Count the newline if we have one. */
+ if (f->next != NULL)
+ totsize++;
+ }
+
+ /* Go through the line at end, if we can. */
+ if (f != NULL) {
+ /* Count the number of characters on this line. */
+ totsize += mbstrlen(f->data);
+
+ /* Count the newline if we have one. */
+ if (f->next != NULL)
+ totsize++;
+ }
+
+ return totsize;
+}
+
+/* Get back a pointer given a line number in the current openfilestruct */
+filestruct *fsfromline(ssize_t lineno)
+{
+ filestruct *f = openfile->current;
+
+ if (lineno <= openfile->current->lineno)
+ for (; f->lineno != lineno && f != openfile->fileage; f = f->prev)
+ ;
+ else
+ for (; f->lineno != lineno && f->next != NULL; f = f->next)
+ ;
+
+ if (f->lineno != lineno)
+ f = NULL;
+ return f;
+}
+
+#ifdef DEBUG
+/* Dump the filestruct inptr to stderr. */
+void dump_filestruct(const filestruct *inptr)
+{
+ if (inptr == openfile->fileage)
+ fprintf(stderr, "Dumping file buffer to stderr...\n");
+ else if (inptr == cutbuffer)
+ fprintf(stderr, "Dumping cutbuffer to stderr...\n");
+ else
+ fprintf(stderr, "Dumping a buffer to stderr...\n");
+
+ while (inptr != NULL) {
+ fprintf(stderr, "(%ld) %s\n", (long)inptr->lineno, inptr->data);
+ inptr = inptr->next;
+ }
+}
+
+/* Dump the current buffer's filestruct to stderr in reverse. */
+void dump_filestruct_reverse(void)
+{
+ const filestruct *fileptr = openfile->filebot;
+
+ while (fileptr != NULL) {
+ fprintf(stderr, "(%ld) %s\n", (long)fileptr->lineno,
+ fileptr->data);
+ fileptr = fileptr->prev;
+ }
+}
+#endif /* DEBUG */
diff --git a/src/winio.c b/src/winio.c
new file mode 100644
index 0000000..3ad7fb1
--- /dev/null
+++ b/src/winio.c
@@ -0,0 +1,3586 @@
+/* $Id: winio.c 4527 2011-02-07 14:45:56Z astyanax $ */
+/**************************************************************************
+ * winio.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 Free Software Foundation, Inc. *
+ * 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 3, 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., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+static int *key_buffer = NULL;
+ /* The keystroke buffer, containing all the keystrokes we
+ * haven't handled yet at a given point. */
+static size_t key_buffer_len = 0;
+ /* The length of the keystroke buffer. */
+static int statusblank = 0;
+ /* The number of keystrokes left after we call statusbar(),
+ * before we actually blank the statusbar. */
+static bool disable_cursorpos = FALSE;
+ /* Should we temporarily disable constant cursor position
+ * display? */
+
+/* Control character compatibility:
+ *
+ * - NANO_BACKSPACE_KEY is Ctrl-H, which is Backspace under ASCII, ANSI,
+ * VT100, and VT220.
+ * - NANO_TAB_KEY is Ctrl-I, which is Tab under ASCII, ANSI, VT100,
+ * VT220, and VT320.
+ * - NANO_ENTER_KEY is Ctrl-M, which is Enter under ASCII, ANSI, VT100,
+ * VT220, and VT320.
+ * - NANO_XON_KEY is Ctrl-Q, which is XON under ASCII, ANSI, VT100,
+ * VT220, and VT320.
+ * - NANO_XOFF_KEY is Ctrl-S, which is XOFF under ASCII, ANSI, VT100,
+ * VT220, and VT320.
+ * - NANO_CONTROL_8 is Ctrl-8 (Ctrl-?), which is Delete under ASCII,
+ * ANSI, VT100, and VT220, and which is Backspace under VT320.
+ *
+ * Note: VT220 and VT320 also generate Esc [ 3 ~ for Delete. By
+ * default, xterm assumes it's running on a VT320 and generates Ctrl-8
+ * (Ctrl-?) for Backspace and Esc [ 3 ~ for Delete. This causes
+ * problems for VT100-derived terminals such as the FreeBSD console,
+ * which expect Ctrl-H for Backspace and Ctrl-8 (Ctrl-?) for Delete, and
+ * on which the VT320 sequences are translated by the keypad to KEY_DC
+ * and [nothing]. We work around this conflict via the REBIND_DELETE
+ * flag: if it's not set, we assume VT320 compatibility, and if it is,
+ * we assume VT100 compatibility. Thanks to Lee Nelson and Wouter van
+ * Hemel for helping work this conflict out.
+ *
+ * Escape sequence compatibility:
+ *
+ * We support escape sequences for ANSI, VT100, VT220, VT320, the Linux
+ * console, the FreeBSD console, the Mach console, xterm, rxvt, Eterm,
+ * and Terminal. Among these, there are several conflicts and
+ * omissions, outlined as follows:
+ *
+ * - Tab on ANSI == PageUp on FreeBSD console; the former is omitted.
+ * (Ctrl-I is also Tab on ANSI, which we already support.)
+ * - PageDown on FreeBSD console == Center (5) on numeric keypad with
+ * NumLock off on Linux console; the latter is omitted. (The editing
+ * keypad key is more important to have working than the numeric
+ * keypad key, because the latter has no value when NumLock is off.)
+ * - F1 on FreeBSD console == the mouse key on xterm/rxvt/Eterm; the
+ * latter is omitted. (Mouse input will only work properly if the
+ * extended keypad value KEY_MOUSE is generated on mouse events
+ * instead of the escape sequence.)
+ * - F9 on FreeBSD console == PageDown on Mach console; the former is
+ * omitted. (The editing keypad is more important to have working
+ * than the function keys, because the functions of the former are not
+ * arbitrary and the functions of the latter are.)
+ * - F10 on FreeBSD console == PageUp on Mach console; the former is
+ * omitted. (Same as above.)
+ * - F13 on FreeBSD console == End on Mach console; the former is
+ * omitted. (Same as above.)
+ * - F15 on FreeBSD console == Shift-Up on rxvt/Eterm; the former is
+ * omitted. (The arrow keys, with or without modifiers, are more
+ * important to have working than the function keys, because the
+ * functions of the former are not arbitrary and the functions of the
+ * latter are.)
+ * - F16 on FreeBSD console == Shift-Down on rxvt/Eterm; the former is
+ * omitted. (Same as above.) */
+
+/* Read in a sequence of keystrokes from win and save them in the
+ * keystroke buffer. This should only be called when the keystroke
+ * buffer is empty. */
+void get_key_buffer(WINDOW *win)
+{
+ int input;
+ size_t errcount;
+
+ /* If the keystroke buffer isn't empty, get out. */
+ if (key_buffer != NULL)
+ return;
+
+ /* Read in the first character using blocking input. */
+#ifndef NANO_TINY
+ allow_pending_sigwinch(TRUE);
+#endif
+
+ /* Just before reading in the first character, display any pending
+ * screen updates. */
+ doupdate();
+
+ errcount = 0;
+ if (nodelay_mode) {
+ if ((input = wgetch(win)) == ERR)
+ return;
+ } else
+ while ((input = wgetch(win)) == ERR) {
+ errcount++;
+
+ /* If we've failed to get a character MAX_BUF_SIZE times in a
+ * row, assume that the input source we were using is gone and
+ * die gracefully. We could check if errno is set to EIO
+ * ("Input/output error") and die gracefully in that case, but
+ * it's not always set properly. Argh. */
+ if (errcount == MAX_BUF_SIZE)
+ handle_hupterm(0);
+ }
+
+#ifndef NANO_TINY
+ allow_pending_sigwinch(FALSE);
+#endif
+
+ /* Increment the length of the keystroke buffer, and save the value
+ * of the keystroke at the end of it. */
+ key_buffer_len++;
+ key_buffer = (int *)nmalloc(sizeof(int));
+ key_buffer[0] = input;
+
+ /* Read in the remaining characters using non-blocking input. */
+ nodelay(win, TRUE);
+
+ while (TRUE) {
+#ifndef NANO_TINY
+ allow_pending_sigwinch(TRUE);
+#endif
+
+ input = wgetch(win);
+
+ /* If there aren't any more characters, stop reading. */
+ if (input == ERR)
+ break;
+
+ /* Otherwise, increment the length of the keystroke buffer, and
+ * save the value of the keystroke at the end of it. */
+ key_buffer_len++;
+ key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
+ sizeof(int));
+ key_buffer[key_buffer_len - 1] = input;
+
+#ifndef NANO_TINY
+ allow_pending_sigwinch(FALSE);
+#endif
+ }
+
+ /* Switch back to non-blocking input. */
+ nodelay(win, FALSE);
+
+#ifdef DEBUG
+ fprintf(stderr, "get_key_buffer(): key_buffer_len = %lu\n", (unsigned long)key_buffer_len);
+#endif
+}
+
+/* Return the length of the keystroke buffer. */
+size_t get_key_buffer_len(void)
+{
+ return key_buffer_len;
+}
+
+/* Add the keystrokes in input to the keystroke buffer. */
+void unget_input(int *input, size_t input_len)
+{
+#ifndef NANO_TINY
+ allow_pending_sigwinch(TRUE);
+ allow_pending_sigwinch(FALSE);
+#endif
+
+ /* If input is empty, get out. */
+ if (input_len == 0)
+ return;
+
+ /* If adding input would put the keystroke buffer beyond maximum
+ * capacity, only add enough of input to put it at maximum
+ * capacity. */
+ if (key_buffer_len + input_len < key_buffer_len)
+ input_len = (size_t)-1 - key_buffer_len;
+
+ /* Add the length of input to the length of the keystroke buffer,
+ * and reallocate the keystroke buffer so that it has enough room
+ * for input. */
+ key_buffer_len += input_len;
+ key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
+ sizeof(int));
+
+ /* If the keystroke buffer wasn't empty before, move its beginning
+ * forward far enough so that we can add input to its beginning. */
+ if (key_buffer_len > input_len)
+ memmove(key_buffer + input_len, key_buffer,
+ (key_buffer_len - input_len) * sizeof(int));
+
+ /* Copy input to the beginning of the keystroke buffer. */
+ memcpy(key_buffer, input, input_len * sizeof(int));
+}
+
+/* Put back the character stored in kbinput, putting it in byte range
+ * beforehand. If meta_key is TRUE, put back the Escape character after
+ * putting back kbinput. If func_key is TRUE, put back the function key
+ * (a value outside byte range) without putting it in byte range. */
+void unget_kbinput(int kbinput, bool meta_key, bool func_key)
+{
+ if (!func_key)
+ kbinput = (char)kbinput;
+
+ unget_input(&kbinput, 1);
+
+ if (meta_key) {
+ kbinput = NANO_CONTROL_3;
+ unget_input(&kbinput, 1);
+ }
+}
+
+/* Try to read input_len characters from the keystroke buffer. If the
+ * keystroke buffer is empty and win isn't NULL, try to read in more
+ * characters from win and add them to the keystroke buffer before doing
+ * anything else. If the keystroke buffer is empty and win is NULL,
+ * return NULL. */
+int *get_input(WINDOW *win, size_t input_len)
+{
+ int *input;
+
+#ifndef NANO_TINY
+ allow_pending_sigwinch(TRUE);
+ allow_pending_sigwinch(FALSE);
+#endif
+
+ if (key_buffer_len == 0) {
+ if (win != NULL) {
+ get_key_buffer(win);
+
+ if (key_buffer_len == 0)
+ return NULL;
+ } else
+ return NULL;
+ }
+
+ /* If input_len is greater than the length of the keystroke buffer,
+ * only read the number of characters in the keystroke buffer. */
+ if (input_len > key_buffer_len)
+ input_len = key_buffer_len;
+
+ /* Subtract input_len from the length of the keystroke buffer, and
+ * allocate input so that it has enough room for input_len
+ * keystrokes. */
+ key_buffer_len -= input_len;
+ input = (int *)nmalloc(input_len * sizeof(int));
+
+ /* Copy input_len keystrokes from the beginning of the keystroke
+ * buffer into input. */
+ memcpy(input, key_buffer, input_len * sizeof(int));
+
+ /* If the keystroke buffer is empty, mark it as such. */
+ if (key_buffer_len == 0) {
+ free(key_buffer);
+ key_buffer = NULL;
+ /* If the keystroke buffer isn't empty, move its beginning forward
+ * far enough so that the keystrokes in input are no longer at its
+ * beginning. */
+ } else {
+ memmove(key_buffer, key_buffer + input_len, key_buffer_len *
+ sizeof(int));
+ key_buffer = (int *)nrealloc(key_buffer, key_buffer_len *
+ sizeof(int));
+ }
+
+ return input;
+}
+
+/* Read in a single character. If it's ignored, swallow it and go on.
+ * Otherwise, try to translate it from ASCII, meta key sequences, escape
+ * sequences, and/or extended keypad values. Set meta_key to TRUE when
+ * we get a meta key sequence, and set func_key to TRUE when we get an
+ * extended keypad value. Supported extended keypad values consist of
+ * [arrow key], Ctrl-[arrow key], Shift-[arrow key], Enter, Backspace,
+ * the editing keypad (Insert, Delete, Home, End, PageUp, and PageDown),
+ * the function keypad (F1-F16), and the numeric keypad with NumLock
+ * off. Assume nodelay(win) is FALSE. */
+int get_kbinput(WINDOW *win, bool *meta_key, bool *func_key)
+{
+ int kbinput;
+
+ /* Read in a character and interpret it. Continue doing this until
+ * we get a recognized value or sequence. */
+ while ((kbinput = parse_kbinput(win, meta_key, func_key)) == ERR);
+
+ /* If we read from the edit window, blank the statusbar if we need
+ * to. */
+ if (win == edit)
+ check_statusblank();
+
+ return kbinput;
+}
+
+/* Translate ASCII characters, extended keypad values, and escape
+ * sequences into their corresponding key values. Set meta_key to TRUE
+ * when we get a meta key sequence, and set func_key to TRUE when we get
+ * a function key. Assume nodelay(win) is FALSE. */
+int parse_kbinput(WINDOW *win, bool *meta_key, bool *func_key)
+{
+ static int escapes = 0, byte_digits = 0;
+ int *kbinput, retval = ERR;
+
+ *meta_key = FALSE;
+ *func_key = FALSE;
+
+ /* Read in a character. */
+ if (nodelay_mode) {
+ kbinput = get_input(win, 1);
+ if (kbinput == 0)
+ return 0;
+ } else
+ while ((kbinput = get_input(win, 1)) == NULL);
+
+ switch (*kbinput) {
+ case ERR:
+ break;
+ case NANO_CONTROL_3:
+ /* Increment the escape counter. */
+ escapes++;
+ switch (escapes) {
+ case 1:
+ /* One escape: wait for more input. */
+ case 2:
+ /* Two escapes: wait for more input. */
+ case 3:
+ /* Three escapes: wait for more input. */
+ break;
+ default:
+ /* More than three escapes: limit the escape counter
+ * to no more than two, and wait for more input. */
+ escapes %= 3;
+ }
+ break;
+ default:
+ switch (escapes) {
+ case 0:
+ /* One non-escape: normal input mode. Save the
+ * non-escape character as the result. */
+ retval = *kbinput;
+ break;
+ case 1:
+ /* Reset the escape counter. */
+ escapes = 0;
+ if (get_key_buffer_len() == 0) {
+ /* One escape followed by a non-escape, and
+ * there aren't any other keystrokes waiting:
+ * meta key sequence mode. Set meta_key to
+ * TRUE, and save the lowercase version of the
+ * non-escape character as the result. */
+ *meta_key = TRUE;
+ retval = tolower(*kbinput);
+ } else
+ /* One escape followed by a non-escape, and
+ * there are other keystrokes waiting: escape
+ * sequence mode. Interpret the escape
+ * sequence. */
+ retval = parse_escape_seq_kbinput(win,
+ *kbinput);
+ break;
+ case 2:
+ if (get_key_buffer_len() == 0) {
+ if (('0' <= *kbinput && *kbinput <= '2' &&
+ byte_digits == 0) || ('0' <= *kbinput &&
+ *kbinput <= '9' && byte_digits > 0)) {
+ /* Two escapes followed by one or more
+ * decimal digits, and there aren't any
+ * other keystrokes waiting: byte sequence
+ * mode. If the byte sequence's range is
+ * limited to 2XX (the first digit is in the
+ * '0' to '2' range and it's the first
+ * digit, or it's in the '0' to '9' range
+ * and it's not the first digit), increment
+ * the byte sequence counter and interpret
+ * the digit. If the byte sequence's range
+ * is not limited to 2XX, fall through. */
+ int byte;
+
+ byte_digits++;
+ byte = get_byte_kbinput(*kbinput);
+
+ if (byte != ERR) {
+ char *byte_mb;
+ int byte_mb_len, *seq, i;
+
+ /* If we've read in a complete byte
+ * sequence, reset the escape counter
+ * and the byte sequence counter, and
+ * put back the corresponding byte
+ * value. */
+ escapes = 0;
+ byte_digits = 0;
+
+ /* Put back the multibyte equivalent of
+ * the byte value. */
+ byte_mb = make_mbchar((long)byte,
+ &byte_mb_len);
+
+ seq = (int *)nmalloc(byte_mb_len *
+ sizeof(int));
+
+ for (i = 0; i < byte_mb_len; i++)
+ seq[i] = (unsigned char)byte_mb[i];
+
+ unget_input(seq, byte_mb_len);
+
+ free(byte_mb);
+ free(seq);
+ }
+ } else {
+ /* Reset the escape counter. */
+ escapes = 0;
+ if (byte_digits == 0)
+ /* Two escapes followed by a non-decimal
+ * digit or a decimal digit that would
+ * create a byte sequence greater than
+ * 2XX, we're not in the middle of a
+ * byte sequence, and there aren't any
+ * other keystrokes waiting: control
+ * character sequence mode. Interpret
+ * the control sequence and save the
+ * corresponding control character as
+ * the result. */
+ retval = get_control_kbinput(*kbinput);
+ else {
+ /* If we're in the middle of a byte
+ * sequence, reset the byte sequence
+ * counter and save the character we got
+ * as the result. */
+ byte_digits = 0;
+ retval = *kbinput;
+ }
+ }
+ } else {
+ /* Two escapes followed by a non-escape, and
+ * there are other keystrokes waiting: combined
+ * meta and escape sequence mode. Reset the
+ * escape counter, set meta_key to TRUE, and
+ * interpret the escape sequence. */
+ escapes = 0;
+ *meta_key = TRUE;
+ retval = parse_escape_seq_kbinput(win,
+ *kbinput);
+ }
+ break;
+ case 3:
+ /* Reset the escape counter. */
+ escapes = 0;
+ if (get_key_buffer_len() == 0)
+ /* Three escapes followed by a non-escape, and
+ * there aren't any other keystrokes waiting:
+ * normal input mode. Save the non-escape
+ * character as the result. */
+ retval = *kbinput;
+ else
+ /* Three escapes followed by a non-escape, and
+ * there are other keystrokes waiting: combined
+ * control character and escape sequence mode.
+ * Interpret the escape sequence, and interpret
+ * the result as a control sequence. */
+ retval = get_control_kbinput(
+ parse_escape_seq_kbinput(win,
+ *kbinput));
+ break;
+ }
+ }
+
+ if (retval != ERR) {
+ switch (retval) {
+ case NANO_CONTROL_8:
+ retval = ISSET(REBIND_DELETE) ? sc_seq_or(do_delete, 0) :
+ sc_seq_or(do_backspace, 0);
+ break;
+ case KEY_DOWN:
+#ifdef KEY_SDOWN
+ /* ncurses and Slang don't support KEY_SDOWN. */
+ case KEY_SDOWN:
+#endif
+ retval = sc_seq_or(do_down_void, *kbinput);
+ break;
+ case KEY_UP:
+#ifdef KEY_SUP
+ /* ncurses and Slang don't support KEY_SUP. */
+ case KEY_SUP:
+#endif
+ retval = sc_seq_or(do_up_void, *kbinput);
+ break;
+ case KEY_LEFT:
+#ifdef KEY_SLEFT
+ /* Slang doesn't support KEY_SLEFT. */
+ case KEY_SLEFT:
+#endif
+ retval = sc_seq_or(do_left, *kbinput);
+ break;
+ case KEY_RIGHT:
+#ifdef KEY_SRIGHT
+ /* Slang doesn't support KEY_SRIGHT. */
+ case KEY_SRIGHT:
+#endif
+ retval = sc_seq_or(do_right, *kbinput);
+ break;
+#ifdef KEY_SHOME
+ /* HP-UX 10-11 and Slang don't support KEY_SHOME. */
+ case KEY_SHOME:
+#endif
+ case KEY_A1: /* Home (7) on numeric keypad with
+ * NumLock off. */
+ retval = sc_seq_or(do_home, *kbinput);
+ break;
+ case KEY_BACKSPACE:
+ retval = sc_seq_or(do_backspace, *kbinput);
+ break;
+#ifdef KEY_SDC
+ /* Slang doesn't support KEY_SDC. */
+ case KEY_SDC:
+ if (ISSET(REBIND_DELETE))
+ retval = sc_seq_or(do_delete, *kbinput);
+ else
+ retval = sc_seq_or(do_backspace, *kbinput);
+ break;
+#endif
+#ifdef KEY_SIC
+ /* Slang doesn't support KEY_SIC. */
+ case KEY_SIC:
+ retval = sc_seq_or(do_insertfile_void, *kbinput);
+ break;
+#endif
+ case KEY_C3: /* PageDown (4) on numeric keypad with
+ * NumLock off. */
+ retval = sc_seq_or(do_page_down, *kbinput);
+ break;
+ case KEY_A3: /* PageUp (9) on numeric keypad with
+ * NumLock off. */
+ retval = sc_seq_or(do_page_up, *kbinput);
+ break;
+ case KEY_ENTER:
+ retval = sc_seq_or(do_enter_void, *kbinput);
+ break;
+ case KEY_B2: /* Center (5) on numeric keypad with
+ * NumLock off. */
+ retval = ERR;
+ break;
+ case KEY_C1: /* End (1) on numeric keypad with
+ * NumLock off. */
+#ifdef KEY_SEND
+ /* HP-UX 10-11 and Slang don't support KEY_SEND. */
+ case KEY_SEND:
+#endif
+ retval = sc_seq_or(do_end, *kbinput);
+ break;
+#ifdef KEY_BEG
+ /* Slang doesn't support KEY_BEG. */
+ case KEY_BEG: /* Center (5) on numeric keypad with
+ * NumLock off. */
+ retval = ERR;
+ break;
+#endif
+#ifdef KEY_CANCEL
+ /* Slang doesn't support KEY_CANCEL. */
+ case KEY_CANCEL:
+#ifdef KEY_SCANCEL
+ /* Slang doesn't support KEY_SCANCEL. */
+ case KEY_SCANCEL:
+#endif
+ retval = first_sc_for(currmenu, do_cancel)->seq;
+ break;
+#endif
+#ifdef KEY_SBEG
+ /* Slang doesn't support KEY_SBEG. */
+ case KEY_SBEG: /* Center (5) on numeric keypad with
+ * NumLock off. */
+ retval = ERR;
+ break;
+#endif
+#ifdef KEY_SSUSPEND
+ /* Slang doesn't support KEY_SSUSPEND. */
+ case KEY_SSUSPEND:
+ retval = sc_seq_or(do_suspend_void, 0);
+ break;
+#endif
+#ifdef KEY_SUSPEND
+ /* Slang doesn't support KEY_SUSPEND. */
+ case KEY_SUSPEND:
+ retval = sc_seq_or(do_suspend_void, 0);
+ break;
+#endif
+#ifdef PDCURSES
+ case KEY_SHIFT_L:
+ case KEY_SHIFT_R:
+ case KEY_CONTROL_L:
+ case KEY_CONTROL_R:
+ case KEY_ALT_L:
+ case KEY_ALT_R:
+ retval = ERR;
+ break;
+#endif
+#if !defined(NANO_TINY) && defined(KEY_RESIZE)
+ /* Since we don't change the default SIGWINCH handler when
+ * NANO_TINY is defined, KEY_RESIZE is never generated.
+ * Also, Slang and SunOS 5.7-5.9 don't support
+ * KEY_RESIZE. */
+ case KEY_RESIZE:
+ retval = ERR;
+ break;
+#endif
+ }
+
+ /* If our result is an extended keypad value (i.e. a value
+ * outside of byte range), set func_key to TRUE. */
+ if (retval != ERR)
+ *func_key = !is_byte(retval);
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "parse_kbinput(): kbinput = %d, meta_key = %s, func_key = %s, escapes = %d, byte_digits = %d, retval = %d\n", *kbinput, *meta_key ? "TRUE" : "FALSE", *func_key ? "TRUE" : "FALSE", escapes, byte_digits, retval);
+#endif
+
+ free(kbinput);
+
+ /* Return the result. */
+ return retval;
+}
+
+/* Translate escape sequences, most of which correspond to extended
+ * keypad values, into their corresponding key values. These sequences
+ * are generated when the keypad doesn't support the needed keys.
+ * Assume that Escape has already been read in. */
+int get_escape_seq_kbinput(const int *seq, size_t seq_len)
+{
+ int retval = ERR;
+
+ if (seq_len > 1) {
+ switch (seq[0]) {
+ case 'O':
+ switch (seq[1]) {
+ case '1':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case ';':
+ if (seq_len >= 4) {
+ switch (seq[3]) {
+ case '2':
+ if (seq_len >= 5) {
+ switch (seq[4]) {
+ case 'A': /* Esc O 1 ; 2 A == Shift-Up on
+ * Terminal. */
+ case 'B': /* Esc O 1 ; 2 B == Shift-Down on
+ * Terminal. */
+ case 'C': /* Esc O 1 ; 2 C == Shift-Right on
+ * Terminal. */
+ case 'D': /* Esc O 1 ; 2 D == Shift-Left on
+ * Terminal. */
+ retval = get_escape_seq_abcd(seq[4]);
+ break;
+ case 'P': /* Esc O 1 ; 2 P == F13 on
+ * Terminal. */
+ retval = KEY_F(13);
+ break;
+ case 'Q': /* Esc O 1 ; 2 Q == F14 on
+ * Terminal. */
+ retval = KEY_F(14);
+ break;
+ case 'R': /* Esc O 1 ; 2 R == F15 on
+ * Terminal. */
+ retval = KEY_F(15);
+ break;
+ case 'S': /* Esc O 1 ; 2 S == F16 on
+ * Terminal. */
+ retval = KEY_F(16);
+ break;
+ }
+ }
+ break;
+ case '5':
+ if (seq_len >= 5) {
+ switch (seq[4]) {
+ case 'A': /* Esc O 1 ; 5 A == Ctrl-Up on
+ * Terminal. */
+ case 'B': /* Esc O 1 ; 5 B == Ctrl-Down on
+ * Terminal. */
+ case 'C': /* Esc O 1 ; 5 C == Ctrl-Right on
+ * Terminal. */
+ case 'D': /* Esc O 1 ; 5 D == Ctrl-Left on
+ * Terminal. */
+ retval = get_escape_seq_abcd(seq[4]);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ case '2':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case 'P': /* Esc O 2 P == F13 on
+ * xterm. */
+ retval = KEY_F(13);
+ break;
+ case 'Q': /* Esc O 2 Q == F14 on
+ * xterm. */
+ retval = KEY_F(14);
+ break;
+ case 'R': /* Esc O 2 R == F15 on
+ * xterm. */
+ retval = KEY_F(15);
+ break;
+ case 'S': /* Esc O 2 S == F16 on
+ * xterm. */
+ retval = KEY_F(16);
+ break;
+ }
+ }
+ break;
+ case 'A': /* Esc O A == Up on VT100/VT320/xterm. */
+ case 'B': /* Esc O B == Down on
+ * VT100/VT320/xterm. */
+ case 'C': /* Esc O C == Right on
+ * VT100/VT320/xterm. */
+ case 'D': /* Esc O D == Left on
+ * VT100/VT320/xterm. */
+ retval = get_escape_seq_abcd(seq[1]);
+ break;
+ case 'E': /* Esc O E == Center (5) on numeric keypad
+ * with NumLock off on xterm. */
+ retval = KEY_B2;
+ break;
+ case 'F': /* Esc O F == End on xterm/Terminal. */
+ retval = sc_seq_or(do_end, 0);
+ break;
+ case 'H': /* Esc O H == Home on xterm/Terminal. */
+ retval = sc_seq_or(do_home, 0);;
+ break;
+ case 'M': /* Esc O M == Enter on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm. */
+ retval = sc_seq_or(do_home, 0);;
+ break;
+ case 'P': /* Esc O P == F1 on VT100/VT220/VT320/Mach
+ * console. */
+ retval = KEY_F(1);
+ break;
+ case 'Q': /* Esc O Q == F2 on VT100/VT220/VT320/Mach
+ * console. */
+ retval = KEY_F(2);
+ break;
+ case 'R': /* Esc O R == F3 on VT100/VT220/VT320/Mach
+ * console. */
+ retval = KEY_F(3);
+ break;
+ case 'S': /* Esc O S == F4 on VT100/VT220/VT320/Mach
+ * console. */
+ retval = KEY_F(4);
+ break;
+ case 'T': /* Esc O T == F5 on Mach console. */
+ retval = KEY_F(5);
+ break;
+ case 'U': /* Esc O U == F6 on Mach console. */
+ retval = KEY_F(6);
+ break;
+ case 'V': /* Esc O V == F7 on Mach console. */
+ retval = KEY_F(7);
+ break;
+ case 'W': /* Esc O W == F8 on Mach console. */
+ retval = KEY_F(8);
+ break;
+ case 'X': /* Esc O X == F9 on Mach console. */
+ retval = KEY_F(9);
+ break;
+ case 'Y': /* Esc O Y == F10 on Mach console. */
+ retval = KEY_F(10);
+ break;
+ case 'a': /* Esc O a == Ctrl-Up on rxvt. */
+ case 'b': /* Esc O b == Ctrl-Down on rxvt. */
+ case 'c': /* Esc O c == Ctrl-Right on rxvt. */
+ case 'd': /* Esc O d == Ctrl-Left on rxvt. */
+ retval = get_escape_seq_abcd(seq[1]);
+ break;
+ case 'j': /* Esc O j == '*' on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm/Terminal. */
+ retval = '*';
+ break;
+ case 'k': /* Esc O k == '+' on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm/Terminal. */
+ retval = '+';
+ break;
+ case 'l': /* Esc O l == ',' on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm/Terminal. */
+ retval = ',';
+ break;
+ case 'm': /* Esc O m == '-' on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm/Terminal. */
+ retval = '-';
+ break;
+ case 'n': /* Esc O n == Delete (.) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * xterm/rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_delete, 0);;
+ break;
+ case 'o': /* Esc O o == '/' on numeric keypad with
+ * NumLock off on VT100/VT220/VT320/xterm/
+ * rxvt/Eterm/Terminal. */
+ retval = '/';
+ break;
+ case 'p': /* Esc O p == Insert (0) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_insertfile_void, 0);;
+ break;
+ case 'q': /* Esc O q == End (1) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_end, 0);;
+ break;
+ case 'r': /* Esc O r == Down (2) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_down_void, 0);;
+ break;
+ case 's': /* Esc O s == PageDown (3) on numeric
+ * keypad with NumLock off on VT100/VT220/
+ * VT320/rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_page_down, 0);;
+ break;
+ case 't': /* Esc O t == Left (4) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_left, 0);;
+ break;
+ case 'u': /* Esc O u == Center (5) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm. */
+ retval = KEY_B2;
+ break;
+ case 'v': /* Esc O v == Right (6) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_right, 0);
+ break;
+ case 'w': /* Esc O w == Home (7) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_home, 0);
+ break;
+ case 'x': /* Esc O x == Up (8) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_up_void, 0);
+ break;
+ case 'y': /* Esc O y == PageUp (9) on numeric keypad
+ * with NumLock off on VT100/VT220/VT320/
+ * rxvt/Eterm/Terminal. */
+ retval = sc_seq_or(do_page_up, 0);
+ break;
+ }
+ break;
+ case 'o':
+ switch (seq[1]) {
+ case 'a': /* Esc o a == Ctrl-Up on Eterm. */
+ case 'b': /* Esc o b == Ctrl-Down on Eterm. */
+ case 'c': /* Esc o c == Ctrl-Right on Eterm. */
+ case 'd': /* Esc o d == Ctrl-Left on Eterm. */
+ retval = get_escape_seq_abcd(seq[1]);
+ break;
+ }
+ break;
+ case '[':
+ switch (seq[1]) {
+ case '1':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case '1': /* Esc [ 1 1 ~ == F1 on rxvt/
+ * Eterm. */
+ retval = KEY_F(1);
+ break;
+ case '2': /* Esc [ 1 2 ~ == F2 on rxvt/
+ * Eterm. */
+ retval = KEY_F(2);
+ break;
+ case '3': /* Esc [ 1 3 ~ == F3 on rxvt/
+ * Eterm. */
+ retval = KEY_F(3);
+ break;
+ case '4': /* Esc [ 1 4 ~ == F4 on rxvt/
+ * Eterm. */
+ retval = KEY_F(4);
+ break;
+ case '5': /* Esc [ 1 5 ~ == F5 on xterm/
+ * rxvt/Eterm. */
+ retval = KEY_F(5);
+ break;
+ case '7': /* Esc [ 1 7 ~ == F6 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(6);
+ break;
+ case '8': /* Esc [ 1 8 ~ == F7 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(7);
+ break;
+ case '9': /* Esc [ 1 9 ~ == F8 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(8);
+ break;
+ case ';':
+ if (seq_len >= 4) {
+ switch (seq[3]) {
+ case '2':
+ if (seq_len >= 5) {
+ switch (seq[4]) {
+ case 'A': /* Esc [ 1 ; 2 A == Shift-Up on
+ * xterm. */
+ case 'B': /* Esc [ 1 ; 2 B == Shift-Down on
+ * xterm. */
+ case 'C': /* Esc [ 1 ; 2 C == Shift-Right on
+ * xterm. */
+ case 'D': /* Esc [ 1 ; 2 D == Shift-Left on
+ * xterm. */
+ retval = get_escape_seq_abcd(seq[4]);
+ break;
+ }
+ }
+ break;
+ case '5':
+ if (seq_len >= 5) {
+ switch (seq[4]) {
+ case 'A': /* Esc [ 1 ; 5 A == Ctrl-Up on
+ * xterm. */
+ case 'B': /* Esc [ 1 ; 5 B == Ctrl-Down on
+ * xterm. */
+ case 'C': /* Esc [ 1 ; 5 C == Ctrl-Right on
+ * xterm. */
+ case 'D': /* Esc [ 1 ; 5 D == Ctrl-Left on
+ * xterm. */
+ retval = get_escape_seq_abcd(seq[4]);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ default: /* Esc [ 1 ~ == Home on
+ * VT320/Linux console. */
+ retval = sc_seq_or(do_home, 0);;
+ break;
+ }
+ }
+ break;
+ case '2':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case '0': /* Esc [ 2 0 ~ == F9 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(9);
+ break;
+ case '1': /* Esc [ 2 1 ~ == F10 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(10);
+ break;
+ case '3': /* Esc [ 2 3 ~ == F11 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(11);
+ break;
+ case '4': /* Esc [ 2 4 ~ == F12 on
+ * VT220/VT320/Linux console/
+ * xterm/rxvt/Eterm. */
+ retval = KEY_F(12);
+ break;
+ case '5': /* Esc [ 2 5 ~ == F13 on
+ * VT220/VT320/Linux console/
+ * rxvt/Eterm. */
+ retval = KEY_F(13);
+ break;
+ case '6': /* Esc [ 2 6 ~ == F14 on
+ * VT220/VT320/Linux console/
+ * rxvt/Eterm. */
+ retval = KEY_F(14);
+ break;
+ case '8': /* Esc [ 2 8 ~ == F15 on
+ * VT220/VT320/Linux console/
+ * rxvt/Eterm. */
+ retval = KEY_F(15);
+ break;
+ case '9': /* Esc [ 2 9 ~ == F16 on
+ * VT220/VT320/Linux console/
+ * rxvt/Eterm. */
+ retval = KEY_F(16);
+ break;
+ default: /* Esc [ 2 ~ == Insert on
+ * VT220/VT320/Linux console/
+ * xterm/Terminal. */
+ retval = sc_seq_or(do_insertfile_void, 0);;
+ break;
+ }
+ }
+ break;
+ case '3': /* Esc [ 3 ~ == Delete on VT220/VT320/
+ * Linux console/xterm/Terminal. */
+ retval = sc_seq_or(do_delete, 0);;
+ break;
+ case '4': /* Esc [ 4 ~ == End on VT220/VT320/Linux
+ * console/xterm. */
+ retval = sc_seq_or(do_end, 0);;
+ break;
+ case '5': /* Esc [ 5 ~ == PageUp on VT220/VT320/
+ * Linux console/xterm/Terminal;
+ * Esc [ 5 ^ == PageUp on Eterm. */
+ retval = sc_seq_or(do_page_up, 0);;
+ break;
+ case '6': /* Esc [ 6 ~ == PageDown on VT220/VT320/
+ * Linux console/xterm/Terminal;
+ * Esc [ 6 ^ == PageDown on Eterm. */
+ retval = sc_seq_or(do_page_down, 0);;
+ break;
+ case '7': /* Esc [ 7 ~ == Home on rxvt. */
+ retval = sc_seq_or(do_home, 0);
+ break;
+ case '8': /* Esc [ 8 ~ == End on rxvt. */
+ retval = sc_seq_or(do_end, 0);
+ break;
+ case '9': /* Esc [ 9 == Delete on Mach console. */
+ retval = sc_seq_or(do_delete, 0);;
+ break;
+ case '@': /* Esc [ @ == Insert on Mach console. */
+ retval = sc_seq_or(do_insertfile_void, 0);;
+ break;
+ case 'A': /* Esc [ A == Up on ANSI/VT220/Linux
+ * console/FreeBSD console/Mach console/
+ * rxvt/Eterm/Terminal. */
+ case 'B': /* Esc [ B == Down on ANSI/VT220/Linux
+ * console/FreeBSD console/Mach console/
+ * rxvt/Eterm/Terminal. */
+ case 'C': /* Esc [ C == Right on ANSI/VT220/Linux
+ * console/FreeBSD console/Mach console/
+ * rxvt/Eterm/Terminal. */
+ case 'D': /* Esc [ D == Left on ANSI/VT220/Linux
+ * console/FreeBSD console/Mach console/
+ * rxvt/Eterm/Terminal. */
+ retval = get_escape_seq_abcd(seq[1]);
+ break;
+ case 'E': /* Esc [ E == Center (5) on numeric keypad
+ * with NumLock off on FreeBSD console/
+ * Terminal. */
+ retval = KEY_B2;
+ break;
+ case 'F': /* Esc [ F == End on FreeBSD
+ * console/Eterm. */
+ retval = sc_seq_or(do_end, 0);
+ break;
+ case 'G': /* Esc [ G == PageDown on FreeBSD
+ * console. */
+ retval = sc_seq_or(do_page_down, 0);
+ break;
+ case 'H': /* Esc [ H == Home on ANSI/VT220/FreeBSD
+ * console/Mach console/Eterm. */
+ retval = sc_seq_or(do_home, 0);
+ break;
+ case 'I': /* Esc [ I == PageUp on FreeBSD
+ * console. */
+ retval = sc_seq_or(do_page_up, 0);
+ break;
+ case 'L': /* Esc [ L == Insert on ANSI/FreeBSD
+ * console. */
+ retval = sc_seq_or(do_insertfile_void, 0);
+ break;
+ case 'M': /* Esc [ M == F1 on FreeBSD console. */
+ retval = KEY_F(1);
+ break;
+ case 'N': /* Esc [ N == F2 on FreeBSD console. */
+ retval = KEY_F(2);
+ break;
+ case 'O':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case 'P': /* Esc [ O P == F1 on
+ * xterm. */
+ retval = KEY_F(1);
+ break;
+ case 'Q': /* Esc [ O Q == F2 on
+ * xterm. */
+ retval = KEY_F(2);
+ break;
+ case 'R': /* Esc [ O R == F3 on
+ * xterm. */
+ retval = KEY_F(3);
+ break;
+ case 'S': /* Esc [ O S == F4 on
+ * xterm. */
+ retval = KEY_F(4);
+ break;
+ }
+ } else
+ /* Esc [ O == F3 on FreeBSD console. */
+ retval = KEY_F(3);
+ break;
+ case 'P': /* Esc [ P == F4 on FreeBSD console. */
+ retval = KEY_F(4);
+ break;
+ case 'Q': /* Esc [ Q == F5 on FreeBSD console. */
+ retval = KEY_F(5);
+ break;
+ case 'R': /* Esc [ R == F6 on FreeBSD console. */
+ retval = KEY_F(6);
+ break;
+ case 'S': /* Esc [ S == F7 on FreeBSD console. */
+ retval = KEY_F(7);
+ break;
+ case 'T': /* Esc [ T == F8 on FreeBSD console. */
+ retval = KEY_F(8);
+ break;
+ case 'U': /* Esc [ U == PageDown on Mach console. */
+ retval = sc_seq_or(do_page_down, 0);
+ break;
+ case 'V': /* Esc [ V == PageUp on Mach console. */
+ retval = sc_seq_or(do_page_up, 0);
+ break;
+ case 'W': /* Esc [ W == F11 on FreeBSD console. */
+ retval = KEY_F(11);
+ break;
+ case 'X': /* Esc [ X == F12 on FreeBSD console. */
+ retval = KEY_F(12);
+ break;
+ case 'Y': /* Esc [ Y == End on Mach console. */
+ retval = sc_seq_or(do_end, 0);
+ break;
+ case 'Z': /* Esc [ Z == F14 on FreeBSD console. */
+ retval = KEY_F(14);
+ break;
+ case 'a': /* Esc [ a == Shift-Up on rxvt/Eterm. */
+ case 'b': /* Esc [ b == Shift-Down on rxvt/Eterm. */
+ case 'c': /* Esc [ c == Shift-Right on rxvt/
+ * Eterm. */
+ case 'd': /* Esc [ d == Shift-Left on rxvt/Eterm. */
+ retval = get_escape_seq_abcd(seq[1]);
+ break;
+ case '[':
+ if (seq_len >= 3) {
+ switch (seq[2]) {
+ case 'A': /* Esc [ [ A == F1 on Linux
+ * console. */
+ retval = KEY_F(1);
+ break;
+ case 'B': /* Esc [ [ B == F2 on Linux
+ * console. */
+ retval = KEY_F(2);
+ break;
+ case 'C': /* Esc [ [ C == F3 on Linux
+ * console. */
+ retval = KEY_F(3);
+ break;
+ case 'D': /* Esc [ [ D == F4 on Linux
+ * console. */
+ retval = KEY_F(4);
+ break;
+ case 'E': /* Esc [ [ E == F5 on Linux
+ * console. */
+ retval = KEY_F(5);
+ break;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "get_escape_seq_kbinput(): retval = %d\n", retval);
+#endif
+
+ return retval;
+}
+
+/* Return the equivalent arrow key value for the case-insensitive
+ * letters A (up), B (down), C (right), and D (left). These are common
+ * to many escape sequences. */
+int get_escape_seq_abcd(int kbinput)
+{
+ switch (tolower(kbinput)) {
+ case 'a':
+ return sc_seq_or(do_up_void, 0);;
+ case 'b':
+ return sc_seq_or(do_down_void, 0);;
+ case 'c':
+ return sc_seq_or(do_right, 0);;
+ case 'd':
+ return sc_seq_or(do_left, 0);;
+ default:
+ return ERR;
+ }
+}
+
+/* Interpret the escape sequence in the keystroke buffer, the first
+ * character of which is kbinput. Assume that the keystroke buffer
+ * isn't empty, and that the initial escape has already been read in. */
+int parse_escape_seq_kbinput(WINDOW *win, int kbinput)
+{
+ int retval, *seq;
+ size_t seq_len;
+
+ /* Put back the non-escape character, get the complete escape
+ * sequence, translate the sequence into its corresponding key
+ * value, and save that as the result. */
+ unget_input(&kbinput, 1);
+ seq_len = get_key_buffer_len();
+ seq = get_input(NULL, seq_len);
+ retval = get_escape_seq_kbinput(seq, seq_len);
+
+ free(seq);
+
+ /* If we got an unrecognized escape sequence, throw it out. */
+ if (retval == ERR) {
+ if (win == edit) {
+ statusbar(_("Unknown Command"));
+ beep();
+ }
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "parse_escape_seq_kbinput(): kbinput = %d, seq_len = %lu, retval = %d\n", kbinput, (unsigned long)seq_len, retval);
+#endif
+
+ return retval;
+}
+
+/* Translate a byte sequence: turn a three-digit decimal number (from
+ * 000 to 255) into its corresponding byte value. */
+int get_byte_kbinput(int kbinput)
+{
+ static int byte_digits = 0, byte = 0;
+ int retval = ERR;
+
+ /* Increment the byte digit counter. */
+ byte_digits++;
+
+ switch (byte_digits) {
+ case 1:
+ /* First digit: This must be from zero to two. Put it in
+ * the 100's position of the byte sequence holder. */
+ if ('0' <= kbinput && kbinput <= '2')
+ byte = (kbinput - '0') * 100;
+ else
+ /* This isn't the start of a byte sequence. Return this
+ * character as the result. */
+ retval = kbinput;
+ break;
+ case 2:
+ /* Second digit: This must be from zero to five if the first
+ * was two, and may be any decimal value if the first was
+ * zero or one. Put it in the 10's position of the byte
+ * sequence holder. */
+ if (('0' <= kbinput && kbinput <= '5') || (byte < 200 &&
+ '6' <= kbinput && kbinput <= '9'))
+ byte += (kbinput - '0') * 10;
+ else
+ /* This isn't the second digit of a byte sequence.
+ * Return this character as the result. */
+ retval = kbinput;
+ break;
+ case 3:
+ /* Third digit: This must be from zero to five if the first
+ * was two and the second was between zero and five, and may
+ * be any decimal value if the first was zero or one and the
+ * second was between six and nine. Put it in the 1's
+ * position of the byte sequence holder. */
+ if (('0' <= kbinput && kbinput <= '5') || (byte < 250 &&
+ '6' <= kbinput && kbinput <= '9')) {
+ byte += kbinput - '0';
+ /* If this character is a valid decimal value, then the
+ * byte sequence is complete. */
+ retval = byte;
+ } else
+ /* This isn't the third digit of a byte sequence.
+ * Return this character as the result. */
+ retval = kbinput;
+ break;
+ default:
+ /* If there are more than three digits, return this
+ * character as the result. (Maybe we should produce an
+ * error instead?) */
+ retval = kbinput;
+ break;
+ }
+
+ /* If we have a result, reset the byte digit counter and the byte
+ * sequence holder. */
+ if (retval != ERR) {
+ byte_digits = 0;
+ byte = 0;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "get_byte_kbinput(): kbinput = %d, byte_digits = %d, byte = %d, retval = %d\n", kbinput, byte_digits, byte, retval);
+#endif
+
+ return retval;
+}
+
+#ifdef ENABLE_UTF8
+/* If the character in kbinput is a valid hexadecimal digit, multiply it
+ * by factor and add the result to uni. */
+long add_unicode_digit(int kbinput, long factor, long *uni)
+{
+ long retval = ERR;
+
+ if ('0' <= kbinput && kbinput <= '9')
+ *uni += (kbinput - '0') * factor;
+ else if ('a' <= tolower(kbinput) && tolower(kbinput) <= 'f')
+ *uni += (tolower(kbinput) - 'a' + 10) * factor;
+ else
+ /* If this character isn't a valid hexadecimal value, save it as
+ * the result. */
+ retval = kbinput;
+
+ return retval;
+}
+
+/* Translate a Unicode sequence: turn a six-digit hexadecimal number
+ * (from 000000 to 10FFFF, case-insensitive) into its corresponding
+ * multibyte value. */
+long get_unicode_kbinput(int kbinput)
+{
+ static int uni_digits = 0;
+ static long uni = 0;
+ long retval = ERR;
+
+ /* Increment the Unicode digit counter. */
+ uni_digits++;
+
+ switch (uni_digits) {
+ case 1:
+ /* First digit: This must be zero or one. Put it in the
+ * 0x100000's position of the Unicode sequence holder. */
+ if ('0' <= kbinput && kbinput <= '1')
+ uni = (kbinput - '0') * 0x100000;
+ else
+ /* This isn't the first digit of a Unicode sequence.
+ * Return this character as the result. */
+ retval = kbinput;
+ break;
+ case 2:
+ /* Second digit: This must be zero if the first was one, and
+ * may be any hexadecimal value if the first was zero. Put
+ * it in the 0x10000's position of the Unicode sequence
+ * holder. */
+ if (uni == 0 || '0' == kbinput)
+ retval = add_unicode_digit(kbinput, 0x10000, &uni);
+ else
+ /* This isn't the second digit of a Unicode sequence.
+ * Return this character as the result. */
+ retval = kbinput;
+ break;
+ case 3:
+ /* Third digit: This may be any hexadecimal value. Put it
+ * in the 0x1000's position of the Unicode sequence
+ * holder. */
+ retval = add_unicode_digit(kbinput, 0x1000, &uni);
+ break;
+ case 4:
+ /* Fourth digit: This may be any hexadecimal value. Put it
+ * in the 0x100's position of the Unicode sequence
+ * holder. */
+ retval = add_unicode_digit(kbinput, 0x100, &uni);
+ break;
+ case 5:
+ /* Fifth digit: This may be any hexadecimal value. Put it
+ * in the 0x10's position of the Unicode sequence holder. */
+ retval = add_unicode_digit(kbinput, 0x10, &uni);
+ break;
+ case 6:
+ /* Sixth digit: This may be any hexadecimal value. Put it
+ * in the 0x1's position of the Unicode sequence holder. */
+ retval = add_unicode_digit(kbinput, 0x1, &uni);
+ /* If this character is a valid hexadecimal value, then the
+ * Unicode sequence is complete. */
+ if (retval == ERR)
+ retval = uni;
+ break;
+ default:
+ /* If there are more than six digits, return this character
+ * as the result. (Maybe we should produce an error
+ * instead?) */
+ retval = kbinput;
+ break;
+ }
+
+ /* If we have a result, reset the Unicode digit counter and the
+ * Unicode sequence holder. */
+ if (retval != ERR) {
+ uni_digits = 0;
+ uni = 0;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "get_unicode_kbinput(): kbinput = %d, uni_digits = %d, uni = %ld, retval = %ld\n", kbinput, uni_digits, uni, retval);
+#endif
+
+ return retval;
+}
+#endif /* ENABLE_UTF8 */
+
+/* Translate a control character sequence: turn an ASCII non-control
+ * character into its corresponding control character. */
+int get_control_kbinput(int kbinput)
+{
+ int retval;
+
+ /* Ctrl-Space (Ctrl-2, Ctrl-@, Ctrl-`) */
+ if (kbinput == ' ' || kbinput == '2')
+ retval = NANO_CONTROL_SPACE;
+ /* Ctrl-/ (Ctrl-7, Ctrl-_) */
+ else if (kbinput == '/')
+ retval = NANO_CONTROL_7;
+ /* Ctrl-3 (Ctrl-[, Esc) to Ctrl-7 (Ctrl-/, Ctrl-_) */
+ else if ('3' <= kbinput && kbinput <= '7')
+ retval = kbinput - 24;
+ /* Ctrl-8 (Ctrl-?) */
+ else if (kbinput == '8' || kbinput == '?')
+ retval = NANO_CONTROL_8;
+ /* Ctrl-@ (Ctrl-Space, Ctrl-2, Ctrl-`) to Ctrl-_ (Ctrl-/, Ctrl-7) */
+ else if ('@' <= kbinput && kbinput <= '_')
+ retval = kbinput - '@';
+ /* Ctrl-` (Ctrl-2, Ctrl-Space, Ctrl-@) to Ctrl-~ (Ctrl-6, Ctrl-^) */
+ else if ('`' <= kbinput && kbinput <= '~')
+ retval = kbinput - '`';
+ else
+ retval = kbinput;
+
+#ifdef DEBUG
+ fprintf(stderr, "get_control_kbinput(): kbinput = %d, retval = %d\n", kbinput, retval);
+#endif
+
+ return retval;
+}
+
+/* Put the output-formatted characters in output back into the keystroke
+ * buffer, so that they can be parsed and displayed as output again. */
+void unparse_kbinput(char *output, size_t output_len)
+{
+ int *input;
+ size_t i;
+
+ if (output_len == 0)
+ return;
+
+ input = (int *)nmalloc(output_len * sizeof(int));
+
+ for (i = 0; i < output_len; i++)
+ input[i] = (int)output[i];
+
+ unget_input(input, output_len);
+
+ free(input);
+}
+
+/* Read in a stream of characters verbatim, and return the length of the
+ * string in kbinput_len. Assume nodelay(win) is FALSE. */
+int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
+{
+ int *retval;
+
+ /* Turn off flow control characters if necessary so that we can type
+ * them in verbatim, and turn the keypad off if necessary so that we
+ * don't get extended keypad values. */
+ if (ISSET(PRESERVE))
+ disable_flow_control();
+ if (!ISSET(REBIND_KEYPAD))
+ keypad(win, FALSE);
+
+ /* Read in a stream of characters and interpret it if possible. */
+ retval = parse_verbatim_kbinput(win, kbinput_len);
+
+ /* Turn flow control characters back on if necessary and turn the
+ * keypad back on if necessary now that we're done. */
+ if (ISSET(PRESERVE))
+ enable_flow_control();
+ if (!ISSET(REBIND_KEYPAD))
+ keypad(win, TRUE);
+
+ return retval;
+}
+
+/* Read in a stream of all available characters, and return the length
+ * of the string in kbinput_len. Translate the first few characters of
+ * the input into the corresponding multibyte value if possible. After
+ * that, leave the input as-is. */
+int *parse_verbatim_kbinput(WINDOW *win, size_t *kbinput_len)
+{
+ int *kbinput, *retval;
+
+ /* Read in the first keystroke. */
+ while ((kbinput = get_input(win, 1)) == NULL);
+
+#ifdef ENABLE_UTF8
+ if (using_utf8()) {
+ /* Check whether the first keystroke is a valid hexadecimal
+ * digit. */
+ long uni = get_unicode_kbinput(*kbinput);
+
+ /* If the first keystroke isn't a valid hexadecimal digit, put
+ * back the first keystroke. */
+ if (uni != ERR)
+ unget_input(kbinput, 1);
+
+ /* Otherwise, read in keystrokes until we have a complete
+ * Unicode sequence, and put back the corresponding Unicode
+ * value. */
+ else {
+ char *uni_mb;
+ int uni_mb_len, *seq, i;
+
+ if (win == edit)
+ /* TRANSLATORS: This is displayed during the input of a
+ * six-digit hexadecimal Unicode character code. */
+ statusbar(_("Unicode Input"));
+
+ while (uni == ERR) {
+ while ((kbinput = get_input(win, 1)) == NULL);
+
+ uni = get_unicode_kbinput(*kbinput);
+ }
+
+ /* Put back the multibyte equivalent of the Unicode
+ * value. */
+ uni_mb = make_mbchar(uni, &uni_mb_len);
+
+ seq = (int *)nmalloc(uni_mb_len * sizeof(int));
+
+ for (i = 0; i < uni_mb_len; i++)
+ seq[i] = (unsigned char)uni_mb[i];
+
+ unget_input(seq, uni_mb_len);
+
+ free(seq);
+ free(uni_mb);
+ }
+ } else
+#endif /* ENABLE_UTF8 */
+
+ /* Put back the first keystroke. */
+ unget_input(kbinput, 1);
+
+ free(kbinput);
+
+ /* Get the complete sequence, and save the characters in it as the
+ * result. */
+ *kbinput_len = get_key_buffer_len();
+ retval = get_input(NULL, *kbinput_len);
+
+ return retval;
+}
+
+#ifndef DISABLE_MOUSE
+/* Handle any mouse event that may have occurred. We currently handle
+ * releases/clicks of the first mouse button. If allow_shortcuts is
+ * TRUE, releasing/clicking on a visible shortcut will put back the
+ * keystroke associated with that shortcut. If NCURSES_MOUSE_VERSION is
+ * at least 2, we also currently handle presses of the fourth mouse
+ * button (upward rolls of the mouse wheel) by putting back the
+ * keystrokes to move up, and presses of the fifth mouse button
+ * (downward rolls of the mouse wheel) by putting back the keystrokes to
+ * move down. We also store the coordinates of a mouse event that needs
+ * to be handled in mouse_x and mouse_y, relative to the entire screen.
+ * Return -1 on error, 0 if the mouse event needs to be handled, 1 if
+ * it's been handled by putting back keystrokes that need to be handled.
+ * or 2 if it's been ignored. Assume that KEY_MOUSE has already been
+ * read in. */
+int get_mouseinput(int *mouse_x, int *mouse_y, bool allow_shortcuts)
+{
+ MEVENT mevent;
+ bool in_bottomwin;
+ subnfunc *f;
+
+ *mouse_x = -1;
+ *mouse_y = -1;
+
+ /* First, get the actual mouse event. */
+ if (getmouse(&mevent) == ERR)
+ return -1;
+
+ /* Save the screen coordinates where the mouse event took place. */
+ *mouse_x = mevent.x;
+ *mouse_y = mevent.y;
+
+ in_bottomwin = wenclose(bottomwin, *mouse_y, *mouse_x);
+
+ /* Handle releases/clicks of the first mouse button. */
+ if (mevent.bstate & (BUTTON1_RELEASED | BUTTON1_CLICKED)) {
+ /* If we're allowing shortcuts, the current shortcut list is
+ * being displayed on the last two lines of the screen, and the
+ * first mouse button was released on/clicked inside it, we need
+ * to figure out which shortcut was released on/clicked and put
+ * back the equivalent keystroke(s) for it. */
+ if (allow_shortcuts && !ISSET(NO_HELP) && in_bottomwin) {
+ int i;
+ /* The width of all the shortcuts, except for the last
+ * two, in the shortcut list in bottomwin. */
+ int j;
+ /* The y-coordinate relative to the beginning of the
+ * shortcut list in bottomwin. */
+ size_t currslen;
+ /* The number of shortcuts in the current shortcut
+ * list. */
+
+ /* Translate the mouse event coordinates so that they're
+ * relative to bottomwin. */
+ wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
+
+ /* Handle releases/clicks of the first mouse button on the
+ * statusbar elsewhere. */
+ if (*mouse_y == 0) {
+ /* Restore the untranslated mouse event coordinates, so
+ * that they're relative to the entire screen again. */
+ *mouse_x = mevent.x;
+ *mouse_y = mevent.y;
+
+ return 0;
+ }
+
+ /* Calculate the y-coordinate relative to the beginning of
+ * the shortcut list in bottomwin. */
+ j = *mouse_y - 1;
+
+ /* Get the shortcut lists' length. */
+ if (currmenu == MMAIN)
+ currslen = MAIN_VISIBLE;
+ else {
+ currslen = length_of_list(currmenu);
+
+ /* We don't show any more shortcuts than the main list
+ * does. */
+ if (currslen > MAIN_VISIBLE)
+ currslen = MAIN_VISIBLE;
+ }
+
+ /* Calculate the width of all of the shortcuts in the list
+ * except for the last two, which are longer by (COLS % i)
+ * columns so as to not waste space. */
+ if (currslen < 2)
+ i = COLS / (MAIN_VISIBLE / 2);
+ else
+ i = COLS / ((currslen / 2) + (currslen % 2));
+
+ /* Calculate the x-coordinate relative to the beginning of
+ * the shortcut list in bottomwin, and add it to j. j
+ * should now be the index in the shortcut list of the
+ * shortcut we released/clicked on. */
+ j = (*mouse_x / i) * 2 + j;
+
+ /* Adjust j if we released on the last two shortcuts. */
+ if ((j >= currslen) && (*mouse_x % i < COLS % i))
+ j -= 2;
+
+ /* Ignore releases/clicks of the first mouse button beyond
+ * the last shortcut. */
+ if (j >= currslen)
+ return 2;
+
+ /* Go through the shortcut list to determine which shortcut
+ * we released/clicked on. */
+ f = allfuncs;
+
+ for (; j > 0; j--) {
+ if (f->next != NULL)
+ f = f->next;
+
+ while (f->next != NULL && ((f->menus & currmenu) == 0
+#ifndef DISABLE_HELP
+ || strlen(f->help) == 0
+#endif
+ ))
+ f = f->next;
+ }
+
+
+ /* And put back the equivalent key. */
+ if (f != NULL) {
+ const sc *s = first_sc_for(currmenu, f->scfunc);
+ if (s != NULL)
+ unget_kbinput(s->seq, s->type == META, FALSE);
+ }
+ } else
+ /* Handle releases/clicks of the first mouse button that
+ * aren't on the current shortcut list elsewhere. */
+ return 0;
+ }
+#if NCURSES_MOUSE_VERSION >= 2
+ /* Handle presses of the fourth mouse button (upward rolls of the
+ * mouse wheel) and presses of the fifth mouse button (downward
+ * rolls of the mouse wheel) . */
+ else if (mevent.bstate & (BUTTON4_PRESSED | BUTTON5_PRESSED)) {
+ bool in_edit = wenclose(edit, *mouse_y, *mouse_x);
+
+ if (in_bottomwin)
+ /* Translate the mouse event coordinates so that they're
+ * relative to bottomwin. */
+ wmouse_trafo(bottomwin, mouse_y, mouse_x, FALSE);
+
+ if (in_edit || (in_bottomwin && *mouse_y == 0)) {
+ int i;
+
+ /* One upward roll of the mouse wheel is equivalent to
+ * moving up three lines, and one downward roll of the mouse
+ * wheel is equivalent to moving down three lines. */
+ for (i = 0; i < 3; i++)
+ unget_kbinput((mevent.bstate & BUTTON4_PRESSED) ?
+ sc_seq_or(up_void, 0) : sc_seq_or(DO_DOWN_VOID, 0), FALSE,
+ FALSE);
+
+ return 1;
+ } else
+ /* Ignore presses of the fourth mouse button and presses of
+ * the fifth mouse buttons that aren't on the edit window or
+ * the statusbar. */
+ return 2;
+ }
+#endif
+
+ /* Ignore all other mouse events. */
+ return 2;
+}
+#endif /* !DISABLE_MOUSE */
+
+/* Return the shortcut corresponding to the values of kbinput (the key
+ * itself), meta_key (whether the key is a meta sequence), and func_key
+ * (whether the key is a function key), if any. The shortcut will be
+ * the first one in the list (control key, meta key sequence, function
+ * key, other meta key sequence) for the corresponding function. For
+ * example, passing in a meta key sequence that corresponds to a
+ * function with a control key, a function key, and a meta key sequence
+ * will return the control key corresponding to that function. */
+const sc *get_shortcut(int menu, int *kbinput, bool
+ *meta_key, bool *func_key)
+{
+ sc *s;
+
+#ifdef DEBUG
+ fprintf(stderr, "get_shortcut(): kbinput = %d, meta_key = %s, func_key = %s\n", *kbinput, *meta_key ? "TRUE" : "FALSE", *func_key ? "TRUE" : "FALSE");
+#endif
+
+ /* Check for shortcuts. */
+ for (s = sclist; s != NULL; s = s->next) {
+ if ((menu & s->menu)
+ && ((s->type == META && *meta_key == TRUE && *kbinput == s->seq)
+ || (s->type != META && *kbinput == s->seq))) {
+#ifdef DEBUG
+ fprintf (stderr, "matched seq \"%s\" and btw meta was %d (menus %d = %d)\n", s->keystr, *meta_key, menu, s->menu);
+#endif
+ return s;
+ }
+ }
+#ifdef DEBUG
+ fprintf (stderr, "matched nothing btw meta was %d\n", *meta_key);
+#endif
+
+ return NULL;
+}
+
+
+/* Try to get a function back from a window. Just a wrapper so
+ functions to need to create function_key meta_key blah blah
+ mmenu - what menu name to look through for valid funcs */
+const subnfunc *getfuncfromkey(WINDOW *win)
+{
+ int kbinput;
+ bool func_key = FALSE, meta_key = FALSE;
+ const sc *s;
+ const subnfunc *f;
+
+ kbinput = parse_kbinput(win, &meta_key, &func_key);
+ if (kbinput == 0)
+ return NULL;
+
+ s = get_shortcut(currmenu, &kbinput, &meta_key, &func_key);
+ if (!s)
+ return NULL;
+
+ f = sctofunc((sc *) s);
+ return f;
+
+}
+
+
+
+/* Move to (x, y) in win, and display a line of n spaces with the
+ * current attributes. */
+void blank_line(WINDOW *win, int y, int x, int n)
+{
+ wmove(win, y, x);
+
+ for (; n > 0; n--)
+ waddch(win, ' ');
+}
+
+/* Blank the first line of the top portion of the window. */
+void blank_titlebar(void)
+{
+ blank_line(topwin, 0, 0, COLS);
+}
+
+/* If the MORE_SPACE flag isn't set, blank the second line of the top
+ * portion of the window. */
+void blank_topbar(void)
+{
+ if (!ISSET(MORE_SPACE))
+ blank_line(topwin, 1, 0, COLS);
+}
+
+/* Blank all the lines of the middle portion of the window, i.e. the
+ * edit window. */
+void blank_edit(void)
+{
+ int i;
+
+ for (i = 0; i < editwinrows; i++)
+ blank_line(edit, i, 0, COLS);
+}
+
+/* Blank the first line of the bottom portion of the window. */
+void blank_statusbar(void)
+{
+ blank_line(bottomwin, 0, 0, COLS);
+}
+
+/* If the NO_HELP flag isn't set, blank the last two lines of the bottom
+ * portion of the window. */
+void blank_bottombars(void)
+{
+ if (!ISSET(NO_HELP)) {
+ blank_line(bottomwin, 1, 0, COLS);
+ blank_line(bottomwin, 2, 0, COLS);
+ }
+}
+
+/* Check if the number of keystrokes needed to blank the statusbar has
+ * been pressed. If so, blank the statusbar, unless constant cursor
+ * position display is on. */
+void check_statusblank(void)
+{
+ if (statusblank > 0) {
+ statusblank--;
+
+ if (statusblank == 0 && !ISSET(CONST_UPDATE)) {
+ blank_statusbar();
+ wnoutrefresh(bottomwin);
+ reset_cursor();
+ wnoutrefresh(edit);
+ }
+ }
+}
+
+/* Convert buf into a string that can be displayed on screen. The
+ * caller wants to display buf starting with column start_col, and
+ * extending for at most len columns. start_col is zero-based. len is
+ * one-based, so len == 0 means you get "" returned. The returned
+ * string is dynamically allocated, and should be freed. If dollars is
+ * TRUE, the caller might put "$" at the beginning or end of the line if
+ * it's too long. */
+char *display_string(const char *buf, size_t start_col, size_t len, bool
+ dollars)
+{
+ size_t start_index;
+ /* Index in buf of the first character shown. */
+ size_t column;
+ /* Screen column that start_index corresponds to. */
+ size_t alloc_len;
+ /* The length of memory allocated for converted. */
+ char *converted;
+ /* The string we return. */
+ size_t index;
+ /* Current position in converted. */
+ char *buf_mb;
+ int buf_mb_len;
+
+ /* If dollars is TRUE, make room for the "$" at the end of the
+ * line. */
+ if (dollars && len > 0 && strlenpt(buf) > start_col + len)
+ len--;
+
+ if (len == 0)
+ return mallocstrcpy(NULL, "");
+
+ buf_mb = charalloc(mb_cur_max());
+
+ start_index = actual_x(buf, start_col);
+ column = strnlenpt(buf, start_index);
+
+ assert(column <= start_col);
+
+ /* Make sure there's enough room for the initial character, whether
+ * it's a multibyte control character, a non-control multibyte
+ * character, a tab character, or a null terminator. Rationale:
+ *
+ * multibyte control character followed by a null terminator:
+ * 1 byte ('^') + mb_cur_max() bytes + 1 byte ('\0')
+ * multibyte non-control character followed by a null terminator:
+ * mb_cur_max() bytes + 1 byte ('\0')
+ * tab character followed by a null terminator:
+ * mb_cur_max() bytes + (tabsize - 1) bytes + 1 byte ('\0')
+ *
+ * Since tabsize has a minimum value of 1, it can substitute for 1
+ * byte above. */
+ alloc_len = (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE;
+ converted = charalloc(alloc_len);
+
+ index = 0;
+
+ if (buf[start_index] != '\0' && buf[start_index] != '\t' &&
+ (column < start_col || (dollars && column > 0))) {
+ /* We don't display all of buf[start_index] since it starts to
+ * the left of the screen. */
+ buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL);
+
+ if (is_cntrl_mbchar(buf_mb)) {
+ if (column < start_col) {
+ char *ctrl_buf_mb = charalloc(mb_cur_max());
+ int ctrl_buf_mb_len, i;
+
+ ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb,
+ &ctrl_buf_mb_len);
+
+ for (i = 0; i < ctrl_buf_mb_len; i++)
+ converted[index++] = ctrl_buf_mb[i];
+
+ start_col += mbwidth(ctrl_buf_mb);
+
+ free(ctrl_buf_mb);
+
+ start_index += buf_mb_len;
+ }
+ }
+#ifdef ENABLE_UTF8
+ else if (using_utf8() && mbwidth(buf_mb) == 2) {
+ if (column >= start_col) {
+ converted[index++] = ' ';
+ start_col++;
+ }
+
+ converted[index++] = ' ';
+ start_col++;
+
+ start_index += buf_mb_len;
+ }
+#endif
+ }
+
+ while (buf[start_index] != '\0') {
+ buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL);
+
+ /* Make sure there's enough room for the next character, whether
+ * it's a multibyte control character, a non-control multibyte
+ * character, a tab character, or a null terminator. */
+ if (index + mb_cur_max() + tabsize + 1 >= alloc_len - 1) {
+ alloc_len += (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE;
+ converted = charealloc(converted, alloc_len);
+ }
+
+ /* If buf contains a tab character, interpret it. */
+ if (*buf_mb == '\t') {
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ if (ISSET(WHITESPACE_DISPLAY)) {
+ int i;
+
+ for (i = 0; i < whitespace_len[0]; i++)
+ converted[index++] = whitespace[i];
+ } else
+#endif
+ converted[index++] = ' ';
+ start_col++;
+ while (start_col % tabsize != 0) {
+ converted[index++] = ' ';
+ start_col++;
+ }
+ /* If buf contains a control character, interpret it. If buf
+ * contains an invalid multibyte control character, display it
+ * as such.*/
+ } else if (is_cntrl_mbchar(buf_mb)) {
+ char *ctrl_buf_mb = charalloc(mb_cur_max());
+ int ctrl_buf_mb_len, i;
+
+ converted[index++] = '^';
+ start_col++;
+
+ ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb,
+ &ctrl_buf_mb_len);
+
+ for (i = 0; i < ctrl_buf_mb_len; i++)
+ converted[index++] = ctrl_buf_mb[i];
+
+ start_col += mbwidth(ctrl_buf_mb);
+
+ free(ctrl_buf_mb);
+ /* If buf contains a space character, interpret it. */
+ } else if (*buf_mb == ' ') {
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ if (ISSET(WHITESPACE_DISPLAY)) {
+ int i;
+
+ for (i = whitespace_len[0]; i < whitespace_len[0] +
+ whitespace_len[1]; i++)
+ converted[index++] = whitespace[i];
+ } else
+#endif
+ converted[index++] = ' ';
+ start_col++;
+ /* If buf contains a non-control character, interpret it. If
+ * buf contains an invalid multibyte non-control character,
+ * display it as such. */
+ } else {
+ char *nctrl_buf_mb = charalloc(mb_cur_max());
+ int nctrl_buf_mb_len, i;
+
+ nctrl_buf_mb = mbrep(buf_mb, nctrl_buf_mb,
+ &nctrl_buf_mb_len);
+
+ for (i = 0; i < nctrl_buf_mb_len; i++)
+ converted[index++] = nctrl_buf_mb[i];
+
+ start_col += mbwidth(nctrl_buf_mb);
+
+ free(nctrl_buf_mb);
+ }
+
+ start_index += buf_mb_len;
+ }
+
+ free(buf_mb);
+
+ assert(alloc_len >= index + 1);
+
+ /* Null-terminate converted. */
+ converted[index] = '\0';
+
+ /* Make sure converted takes up no more than len columns. */
+ index = actual_x(converted, len);
+ null_at(&converted, index);
+
+ return converted;
+}
+
+/* If path is NULL, we're in normal editing mode, so display the current
+ * version of nano, the current filename, and whether the current file
+ * has been modified on the titlebar. If path isn't NULL, we're in the
+ * file browser, and path contains the directory to start the file
+ * browser in, so display the current version of nano and the contents
+ * of path on the titlebar. */
+void titlebar(const char *path)
+{
+ int space = COLS;
+ /* The space we have available for display. */
+ size_t verlen = strlenpt(PACKAGE_STRING) + 1;
+ /* The length of the version message in columns, plus one for
+ * padding. */
+ const char *prefix;
+ /* "DIR:", "File:", or "New Buffer". Goes before filename. */
+ size_t prefixlen;
+ /* The length of the prefix in columns, plus one for padding. */
+ const char *state;
+ /* "Modified", "View", or "". Shows the state of this
+ * buffer. */
+ size_t statelen = 0;
+ /* The length of the state in columns, or the length of
+ * "Modified" if the state is blank and we're not in the file
+ * browser. */
+ char *exppath = NULL;
+ /* The filename, expanded for display. */
+ bool newfie = FALSE;
+ /* Do we say "New Buffer"? */
+ bool dots = FALSE;
+ /* Do we put an ellipsis before the path? */
+
+ assert(path != NULL || openfile->filename != NULL);
+
+ wattron(topwin, reverse_attr);
+
+ blank_titlebar();
+
+ /* space has to be at least 4: two spaces before the version message,
+ * at least one character of the version message, and one space
+ * after the version message. */
+ if (space < 4)
+ space = 0;
+ else {
+ /* Limit verlen to 1/3 the length of the screen in columns,
+ * minus three columns for spaces. */
+ if (verlen > (COLS / 3) - 3)
+ verlen = (COLS / 3) - 3;
+ }
+
+ if (space >= 4) {
+ /* Add a space after the version message, and account for both
+ * it and the two spaces before it. */
+ mvwaddnstr(topwin, 0, 2, PACKAGE_STRING,
+ actual_x(PACKAGE_STRING, verlen));
+ verlen += 3;
+
+ /* Account for the full length of the version message. */
+ space -= verlen;
+ }
+
+#ifndef DISABLE_BROWSER
+ /* Don't display the state if we're in the file browser. */
+ if (path != NULL)
+ state = "";
+ else
+#endif
+ state = openfile->modified ? _("Modified") : ISSET(VIEW_MODE) ?
+ _("View") : "";
+
+ statelen = strlenpt((*state == '\0' && path == NULL) ?
+ _("Modified") : state);
+
+ /* If possible, add a space before state. */
+ if (space > 0 && statelen < space)
+ statelen++;
+ else
+ goto the_end;
+
+#ifndef DISABLE_BROWSER
+ /* path should be a directory if we're in the file browser. */
+ if (path != NULL)
+ prefix = _("DIR:");
+ else
+#endif
+ if (openfile->filename[0] == '\0') {
+ prefix = _("New Buffer");
+ newfie = TRUE;
+ } else
+ prefix = _("File:");
+
+ prefixlen = strnlenpt(prefix, space - statelen) + 1;
+
+ /* If newfie is FALSE, add a space after prefix. */
+ if (!newfie && prefixlen + statelen < space)
+ prefixlen++;
+
+ /* If we're not in the file browser, set path to the current
+ * filename. */
+ if (path == NULL)
+ path = openfile->filename;
+
+ /* Account for the full lengths of the prefix and the state. */
+ if (space >= prefixlen + statelen)
+ space -= prefixlen + statelen;
+ else
+ space = 0;
+ /* space is now the room we have for the filename. */
+
+ if (!newfie) {
+ size_t lenpt = strlenpt(path), start_col;
+
+ /* Don't set dots to TRUE if we have fewer than eight columns
+ * (i.e. one column for padding, plus seven columns for a
+ * filename). */
+ dots = (space >= 8 && lenpt >= space);
+
+ if (dots) {
+ start_col = lenpt - space + 3;
+ space -= 3;
+ } else
+ start_col = 0;
+
+ exppath = display_string(path, start_col, space, FALSE);
+ }
+
+ /* If dots is TRUE, we will display something like "File:
+ * ...ename". */
+ if (dots) {
+ mvwaddnstr(topwin, 0, verlen - 1, prefix, actual_x(prefix,
+ prefixlen));
+ if (space <= -3 || newfie)
+ goto the_end;
+ waddch(topwin, ' ');
+ waddnstr(topwin, "...", space + 3);
+ if (space <= 0)
+ goto the_end;
+ waddstr(topwin, exppath);
+ } else {
+ size_t exppathlen = newfie ? 0 : strlenpt(exppath);
+ /* The length of the expanded filename. */
+
+ /* There is room for the whole filename, so we center it. */
+ mvwaddnstr(topwin, 0, verlen + ((space - exppathlen) / 3),
+ prefix, actual_x(prefix, prefixlen));
+ if (!newfie) {
+ waddch(topwin, ' ');
+ waddstr(topwin, exppath);
+ }
+ }
+
+ the_end:
+ free(exppath);
+
+ if (state[0] != '\0') {
+ if (statelen >= COLS - 1)
+ mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS));
+ else {
+ assert(COLS - statelen - 1 >= 0);
+
+ mvwaddnstr(topwin, 0, COLS - statelen - 1, state,
+ actual_x(state, statelen));
+ }
+ }
+
+ wattroff(topwin, reverse_attr);
+
+ wnoutrefresh(topwin);
+ reset_cursor();
+ wnoutrefresh(edit);
+}
+
+/* Mark the current file as modified if it isn't already, and then
+ * update the titlebar to display the file's new status. */
+void set_modified(void)
+{
+ if (!openfile->modified) {
+ openfile->modified = TRUE;
+ titlebar(NULL);
+ }
+}
+
+/* Display a message on the statusbar, and set disable_cursorpos to
+ * TRUE, so that the message won't be immediately overwritten if
+ * constant cursor position display is on. */
+void statusbar(const char *msg, ...)
+{
+ va_list ap;
+ char *bar, *foo;
+ size_t start_x, foo_len;
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ bool old_whitespace;
+#endif
+
+ va_start(ap, msg);
+
+ /* Curses mode is turned off. If we use wmove() now, it will muck
+ * up the terminal settings. So we just use vfprintf(). */
+ if (isendwin()) {
+ vfprintf(stderr, msg, ap);
+ va_end(ap);
+ return;
+ }
+
+ blank_statusbar();
+
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ old_whitespace = ISSET(WHITESPACE_DISPLAY);
+ UNSET(WHITESPACE_DISPLAY);
+#endif
+ bar = charalloc(mb_cur_max() * (COLS - 3));
+ vsnprintf(bar, mb_cur_max() * (COLS - 3), msg, ap);
+ va_end(ap);
+ foo = display_string(bar, 0, COLS - 4, FALSE);
+#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
+ if (old_whitespace)
+ SET(WHITESPACE_DISPLAY);
+#endif
+ free(bar);
+ foo_len = strlenpt(foo);
+ start_x = (COLS - foo_len - 4) / 2;
+
+ wmove(bottomwin, 0, start_x);
+ wattron(bottomwin, reverse_attr);
+ waddstr(bottomwin, "[ ");
+ waddstr(bottomwin, foo);
+ free(foo);
+ waddstr(bottomwin, " ]");
+ wattroff(bottomwin, reverse_attr);
+ wnoutrefresh(bottomwin);
+ reset_cursor();
+ wnoutrefresh(edit);
+ /* Leave the cursor at its position in the edit window, not in
+ * the statusbar. */
+
+ disable_cursorpos = TRUE;
+
+ /* If we're doing quick statusbar blanking, and constant cursor
+ * position display is off, blank the statusbar after only one
+ * keystroke. Otherwise, blank it after twenty-six keystrokes, as
+ * Pico does. */
+ statusblank =
+#ifndef NANO_TINY
+ ISSET(QUICK_BLANK) && !ISSET(CONST_UPDATE) ? 1 :
+#endif
+ 26;
+}
+
+/* Display the shortcut list in s on the last two rows of the bottom
+ * portion of the window. */
+void bottombars(int menu)
+{
+ size_t i, colwidth, slen;
+ subnfunc *f;
+ const sc *s;
+
+ if (ISSET(NO_HELP))
+ return;
+
+ if (menu == MMAIN) {
+ slen = MAIN_VISIBLE;
+
+ assert(slen <= length_of_list(menu));
+ } else {
+ slen = length_of_list(menu);
+
+ /* Don't show any more shortcuts than the main list does. */
+ if (slen > MAIN_VISIBLE)
+ slen = MAIN_VISIBLE;
+ }
+
+ /* There will be this many characters per column, except for the
+ * last two, which will be longer by (COLS % colwidth) columns so as
+ * to not waste space. We need at least three columns to display
+ * anything properly. */
+ colwidth = COLS / ((slen / 2) + (slen % 2));
+
+ blank_bottombars();
+
+#ifdef DEBUG
+ fprintf(stderr, "In bottombars, and slen == \"%d\"\n", (int) slen);
+#endif
+
+ for (f = allfuncs, i = 0; i < slen && f != NULL; f = f->next) {
+
+#ifdef DEBUG
+ fprintf(stderr, "Checking menu items....");
+#endif
+ if ((f->menus & menu) == 0)
+ continue;
+
+ if (!f->desc || strlen(f->desc) == 0)
+ continue;
+
+#ifdef DEBUG
+ fprintf(stderr, "found one! f->menus = %d, desc = \"%s\"\n", f->menus, f->desc);
+#endif
+ s = first_sc_for(menu, f->scfunc);
+ if (s == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "Whoops, guess not, no shortcut key found for func!\n");
+#endif
+ continue;
+ }
+ wmove(bottomwin, 1 + i % 2, (i / 2) * colwidth);
+#ifdef DEBUG
+ fprintf(stderr, "Calling onekey with keystr \"%s\" and desc \"%s\"\n", s->keystr, f->desc);
+#endif
+ onekey(s->keystr, _(f->desc), colwidth + (COLS % colwidth));
+ i++;
+ }
+
+ wnoutrefresh(bottomwin);
+ reset_cursor();
+ wnoutrefresh(edit);
+}
+
+/* Write a shortcut key to the help area at the bottom of the window.
+ * keystroke is e.g. "^G" and desc is e.g. "Get Help". We are careful
+ * to write at most len characters, even if len is very small and
+ * keystroke and desc are long. Note that waddnstr(,,(size_t)-1) adds
+ * the whole string! We do not bother padding the entry with blanks. */
+void onekey(const char *keystroke, const char *desc, size_t len)
+{
+ size_t keystroke_len = strlenpt(keystroke) + 1;
+
+ assert(keystroke != NULL && desc != NULL);
+
+ wattron(bottomwin, reverse_attr);
+ waddnstr(bottomwin, keystroke, actual_x(keystroke, len));
+ wattroff(bottomwin, reverse_attr);
+
+ if (len > keystroke_len)
+ len -= keystroke_len;
+ else
+ len = 0;
+
+ if (len > 0) {
+ waddch(bottomwin, ' ');
+ waddnstr(bottomwin, desc, actual_x(desc, len));
+ }
+}
+
+/* Reset current_y, based on the position of current, and put the cursor
+ * in the edit window at (current_y, current_x). */
+void reset_cursor(void)
+{
+ size_t xpt;
+ /* If we haven't opened any files yet, put the cursor in the top
+ * left corner of the edit window and get out. */
+ if (openfile == NULL) {
+ wmove(edit, 0, 0);
+ return;
+ }
+
+ xpt = xplustabs();
+
+ if (ISSET(SOFTWRAP)) {
+ filestruct *tmp;
+ openfile->current_y = 0;
+
+ for (tmp = openfile->edittop; tmp && tmp != openfile->current; tmp = tmp->next)
+ openfile->current_y += 1 + strlenpt(tmp->data) / COLS;
+
+ openfile->current_y += xplustabs() / COLS;
+ if (openfile->current_y < editwinrows)
+ wmove(edit, openfile->current_y, xpt % COLS);
+ } else {
+ openfile->current_y = openfile->current->lineno -
+ openfile->edittop->lineno;
+
+ if (openfile->current_y < editwinrows)
+ wmove(edit, openfile->current_y, xpt - get_page_start(xpt));
+ }
+}
+
+/* edit_draw() takes care of the job of actually painting a line into
+ * the edit window. fileptr is the line to be painted, at row line of
+ * the window. converted is the actual string to be written to the
+ * window, with tabs and control characters replaced by strings of
+ * regular characters. start is the column number of the first
+ * character of this page. That is, the first character of converted
+ * corresponds to character number actual_x(fileptr->data, start) of the
+ * line. */
+void edit_draw(filestruct *fileptr, const char *converted, int
+ line, size_t start)
+{
+#if !defined(NANO_TINY) || defined(ENABLE_COLOR)
+ size_t startpos = actual_x(fileptr->data, start);
+ /* The position in fileptr->data of the leftmost character
+ * that displays at least partially on the window. */
+ size_t endpos = actual_x(fileptr->data, start + COLS - 1) + 1;
+ /* The position in fileptr->data of the first character that is
+ * completely off the window to the right.
+ *
+ * Note that endpos might be beyond the null terminator of the
+ * string. */
+#endif
+
+ assert(openfile != NULL && fileptr != NULL && converted != NULL);
+ assert(strlenpt(converted) <= COLS);
+
+ /* Just paint the string in any case (we'll add color or reverse on
+ * just the text that needs it). */
+ mvwaddstr(edit, line, 0, converted);
+
+#ifdef ENABLE_COLOR
+ /* If color syntaxes are available and turned on, we need to display
+ * them. */
+ if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) {
+ const colortype *tmpcolor = openfile->colorstrings;
+
+ /* Set up multi-line color data for this line if it's not yet calculated */
+ if (fileptr->multidata == NULL && openfile->syntax
+ && openfile->syntax->nmultis > 0) {
+ int i;
+ fileptr->multidata = (short *) nmalloc(openfile->syntax->nmultis * sizeof(short));
+ for (i = 0; i < openfile->syntax->nmultis; i++)
+ fileptr->multidata[i] = -1; /* Assue this applies until we know otherwise */
+ }
+ for (; tmpcolor != NULL; tmpcolor = tmpcolor->next) {
+ int x_start;
+ /* Starting column for mvwaddnstr. Zero-based. */
+ int paintlen;
+ /* Number of chars to paint on this line. There are
+ * COLS characters on a whole line. */
+ size_t index;
+ /* Index in converted where we paint. */
+ regmatch_t startmatch;
+ /* Match position for start_regex. */
+ regmatch_t endmatch;
+ /* Match position for end_regex. */
+
+ if (tmpcolor->bright)
+ wattron(edit, A_BOLD);
+ wattron(edit, COLOR_PAIR(tmpcolor->pairnum));
+ /* Two notes about regexec(). A return value of zero means
+ * that there is a match. Also, rm_eo is the first
+ * non-matching character after the match. */
+
+ /* First case, tmpcolor is a single-line expression. */
+ if (tmpcolor->end == NULL) {
+ size_t k = 0;
+
+ /* We increment k by rm_eo, to move past the end of the
+ * last match. Even though two matches may overlap, we
+ * want to ignore them, so that we can highlight e.g. C
+ * strings correctly. */
+ while (k < endpos) {
+ /* Note the fifth parameter to regexec(). It says
+ * not to match the beginning-of-line character
+ * unless k is zero. If regexec() returns
+ * REG_NOMATCH, there are no more matches in the
+ * line. */
+ if (regexec(tmpcolor->start, &fileptr->data[k], 1,
+ &startmatch, (k == 0) ? 0 : REG_NOTBOL) ==
+ REG_NOMATCH)
+ break;
+ /* Translate the match to the beginning of the
+ * line. */
+ startmatch.rm_so += k;
+ startmatch.rm_eo += k;
+
+ /* Skip over a zero-length regex match. */
+ if (startmatch.rm_so == startmatch.rm_eo)
+ startmatch.rm_eo++;
+ else if (startmatch.rm_so < endpos &&
+ startmatch.rm_eo > startpos) {
+ x_start = (startmatch.rm_so <= startpos) ? 0 :
+ strnlenpt(fileptr->data,
+ startmatch.rm_so) - start;
+
+ index = actual_x(converted, x_start);
+
+ paintlen = actual_x(converted + index,
+ strnlenpt(fileptr->data,
+ startmatch.rm_eo) - start - x_start);
+
+ assert(0 <= x_start && 0 <= paintlen);
+
+ mvwaddnstr(edit, line, x_start, converted +
+ index, paintlen);
+ }
+ k = startmatch.rm_eo;
+ }
+ } else if (fileptr->multidata != NULL && fileptr->multidata[tmpcolor->id] != CNONE) {
+ /* This is a multi-line regex. There are two steps.
+ * First, we have to see if the beginning of the line is
+ * colored by a start on an earlier line, and an end on
+ * this line or later.
+ *
+ * We find the first line before fileptr matching the
+ * start. If every match on that line is followed by an
+ * end, then go to step two. Otherwise, find the next
+ * line after start_line matching the end. If that line
+ * is not before fileptr, then paint the beginning of
+ * this line. */
+ const filestruct *start_line = fileptr->prev;
+ /* The first line before fileptr matching start. */
+ regoff_t start_col;
+ /* Where it starts in that line. */
+ const filestruct *end_line;
+ short md = fileptr->multidata[tmpcolor->id];
+
+ if (md == -1)
+ fileptr->multidata[tmpcolor->id] = CNONE; /* until we find out otherwise */
+ else if (md == CNONE)
+ continue;
+ else if (md == CWHOLELINE) {
+ mvwaddnstr(edit, line, 0, converted, -1);
+ continue;
+ } else if (md == CBEGINBEFORE) {
+ regexec(tmpcolor->end, fileptr->data, 1, &endmatch, 0);
+ paintlen = actual_x(converted, strnlenpt(fileptr->data,
+ endmatch.rm_eo) - start);
+ mvwaddnstr(edit, line, 0, converted, paintlen);
+ continue;
+ }
+
+ while (start_line != NULL && regexec(tmpcolor->start,
+ start_line->data, 1, &startmatch, 0) ==
+ REG_NOMATCH) {
+ /* If there is an end on this line, there is no need
+ * to look for starts on earlier lines. */
+ if (regexec(tmpcolor->end, start_line->data, 0,
+ NULL, 0) == 0)
+ goto step_two;
+ start_line = start_line->prev;
+ }
+
+ /* Skip over a zero-length regex match. */
+ if (startmatch.rm_so == startmatch.rm_eo)
+ startmatch.rm_eo++;
+ else {
+ /* No start found, so skip to the next step. */
+ if (start_line == NULL)
+ goto step_two;
+ /* Now start_line is the first line before fileptr
+ * containing a start match. Is there a start on
+ * this line not followed by an end on this line? */
+ start_col = 0;
+ while (TRUE) {
+ start_col += startmatch.rm_so;
+ startmatch.rm_eo -= startmatch.rm_so;
+ if (regexec(tmpcolor->end, start_line->data +
+ start_col + startmatch.rm_eo, 0, NULL,
+ (start_col + startmatch.rm_eo == 0) ?
+ 0 : REG_NOTBOL) == REG_NOMATCH)
+ /* No end found after this start. */
+ break;
+ start_col++;
+ if (regexec(tmpcolor->start, start_line->data +
+ start_col, 1, &startmatch,
+ REG_NOTBOL) == REG_NOMATCH)
+ /* No later start on this line. */
+ goto step_two;
+ }
+ /* Indeed, there is a start not followed on this
+ * line by an end. */
+
+ /* We have already checked that there is no end
+ * before fileptr and after the start. Is there an
+ * end after the start at all? We don't paint
+ * unterminated starts. */
+ end_line = fileptr;
+ while (end_line != NULL && regexec(tmpcolor->end,
+ end_line->data, 1, &endmatch, 0) == REG_NOMATCH)
+ end_line = end_line->next;
+
+ /* No end found, or it is too early. */
+ if (end_line == NULL || (end_line == fileptr &&
+ endmatch.rm_eo <= startpos))
+ goto step_two;
+
+ /* Now paint the start of fileptr. If the start of
+ * fileptr is on a different line from the end,
+ * paintlen is -1, meaning that everything on the
+ * line gets painted. Otherwise, paintlen is the
+ * expanded location of the end of the match minus
+ * the expanded location of the beginning of the
+ * page. */
+ if (end_line != fileptr) {
+ paintlen = -1;
+ fileptr->multidata[tmpcolor->id] = CWHOLELINE;
+ } else {
+ paintlen = actual_x(converted,
+ strnlenpt(fileptr->data,
+ endmatch.rm_eo) - start);
+ fileptr->multidata[tmpcolor->id] = CBEGINBEFORE;
+ }
+ mvwaddnstr(edit, line, 0, converted, paintlen);
+ step_two:
+ /* Second step, we look for starts on this line. */
+ start_col = 0;
+
+ while (start_col < endpos) {
+ if (regexec(tmpcolor->start, fileptr->data +
+ start_col, 1, &startmatch, (start_col ==
+ 0) ? 0 : REG_NOTBOL) == REG_NOMATCH ||
+ start_col + startmatch.rm_so >= endpos)
+ /* No more starts on this line. */
+ break;
+ /* Translate the match to be relative to the
+ * beginning of the line. */
+ startmatch.rm_so += start_col;
+ startmatch.rm_eo += start_col;
+
+ x_start = (startmatch.rm_so <= startpos) ? 0 :
+ strnlenpt(fileptr->data,
+ startmatch.rm_so) - start;
+
+ index = actual_x(converted, x_start);
+
+ if (regexec(tmpcolor->end, fileptr->data +
+ startmatch.rm_eo, 1, &endmatch,
+ (startmatch.rm_eo == 0) ? 0 :
+ REG_NOTBOL) == 0) {
+ /* Translate the end match to be relative to
+ * the beginning of the line. */
+ endmatch.rm_so += startmatch.rm_eo;
+ endmatch.rm_eo += startmatch.rm_eo;
+ /* There is an end on this line. But does
+ * it appear on this page, and is the match
+ * more than zero characters long? */
+ if (endmatch.rm_eo > startpos &&
+ endmatch.rm_eo > startmatch.rm_so) {
+ paintlen = actual_x(converted + index,
+ strnlenpt(fileptr->data,
+ endmatch.rm_eo) - start -
+ x_start);
+
+ assert(0 <= x_start && x_start < COLS);
+
+ mvwaddnstr(edit, line, x_start,
+ converted + index, paintlen);
+ if (paintlen > 0)
+ fileptr->multidata[tmpcolor->id] = CSTARTENDHERE;
+
+ }
+ } else {
+ /* There is no end on this line. But we
+ * haven't yet looked for one on later
+ * lines. */
+ end_line = fileptr->next;
+
+ while (end_line != NULL &&
+ regexec(tmpcolor->end, end_line->data,
+ 0, NULL, 0) == REG_NOMATCH)
+ end_line = end_line->next;
+
+ if (end_line != NULL) {
+ assert(0 <= x_start && x_start < COLS);
+
+ mvwaddnstr(edit, line, x_start,
+ converted + index, -1);
+ /* We painted to the end of the line, so
+ * don't bother checking any more
+ * starts. */
+ fileptr->multidata[tmpcolor->id] = CENDAFTER;
+ break;
+ }
+ }
+ start_col = startmatch.rm_so + 1;
+ }
+ }
+ }
+
+ wattroff(edit, A_BOLD);
+ wattroff(edit, COLOR_PAIR(tmpcolor->pairnum));
+ }
+ }
+#endif /* ENABLE_COLOR */
+
+#ifndef NANO_TINY
+ /* If the mark is on, we need to display it. */
+ if (openfile->mark_set && (fileptr->lineno <=
+ openfile->mark_begin->lineno || fileptr->lineno <=
+ openfile->current->lineno) && (fileptr->lineno >=
+ openfile->mark_begin->lineno || fileptr->lineno >=
+ openfile->current->lineno)) {
+ /* fileptr is at least partially selected. */
+ const filestruct *top;
+ /* Either current or mark_begin, whichever is first. */
+ size_t top_x;
+ /* current_x or mark_begin_x, corresponding to top. */
+ const filestruct *bot;
+ size_t bot_x;
+ int x_start;
+ /* Starting column for mvwaddnstr(). Zero-based. */
+ int paintlen;
+ /* Number of characters to paint on this line. There are
+ * COLS characters on a whole line. */
+ size_t index;
+ /* Index in converted where we paint. */
+
+ mark_order(&top, &top_x, &bot, &bot_x, NULL);
+
+ if (top->lineno < fileptr->lineno || top_x < startpos)
+ top_x = startpos;
+ if (bot->lineno > fileptr->lineno || bot_x > endpos)
+ bot_x = endpos;
+
+ /* The selected bit of fileptr is on this page. */
+ if (top_x < endpos && bot_x > startpos) {
+ assert(startpos <= top_x);
+
+ /* x_start is the expanded location of the beginning of the
+ * mark minus the beginning of the page. */
+ x_start = strnlenpt(fileptr->data, top_x) - start;
+
+ /* If the end of the mark is off the page, paintlen is -1,
+ * meaning that everything on the line gets painted.
+ * Otherwise, paintlen is the expanded location of the end
+ * of the mark minus the expanded location of the beginning
+ * of the mark. */
+ if (bot_x >= endpos)
+ paintlen = -1;
+ else
+ paintlen = strnlenpt(fileptr->data, bot_x) - (x_start +
+ start);
+
+ /* If x_start is before the beginning of the page, shift
+ * paintlen x_start characters to compensate, and put
+ * x_start at the beginning of the page. */
+ if (x_start < 0) {
+ paintlen += x_start;
+ x_start = 0;
+ }
+
+ assert(x_start >= 0 && x_start <= strlen(converted));
+
+ index = actual_x(converted, x_start);
+
+ if (paintlen > 0)
+ paintlen = actual_x(converted + index, paintlen);
+
+ wattron(edit, reverse_attr);
+ mvwaddnstr(edit, line, x_start, converted + index,
+ paintlen);
+ wattroff(edit, reverse_attr);
+ }
+ }
+#endif /* !NANO_TINY */
+}
+
+/* Just update one line in the edit buffer. This is basically a wrapper
+ * for edit_draw(). The line will be displayed starting with
+ * fileptr->data[index]. Likely arguments are current_x or zero.
+ * Returns: Number of additiona lines consumed (needed for SOFTWRAP)
+ */
+int update_line(filestruct *fileptr, size_t index)
+{
+ int line = 0;
+ int extralinesused = 0;
+ /* The line in the edit window that we want to update. */
+ char *converted;
+ /* fileptr->data converted to have tabs and control characters
+ * expanded. */
+ size_t page_start;
+ filestruct *tmp;
+
+ assert(fileptr != NULL);
+
+ if (ISSET(SOFTWRAP)) {
+ for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next) {
+ line += 1 + (strlenpt(tmp->data) / COLS);
+ }
+ } else
+ line = fileptr->lineno - openfile->edittop->lineno;
+
+ if (line < 0 || line >= editwinrows)
+ return 1;
+
+ /* First, blank out the line. */
+ blank_line(edit, line, 0, COLS);
+
+ /* Next, convert variables that index the line to their equivalent
+ * positions in the expanded line. */
+ if (ISSET(SOFTWRAP))
+ index = 0;
+ else
+ index = strnlenpt(fileptr->data, index);
+ page_start = get_page_start(index);
+
+ /* Expand the line, replacing tabs with spaces, and control
+ * characters with their displayed forms. */
+ converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP));
+
+#ifdef DEBUG
+ if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
+ fprintf(stderr, "update_line(): converted(1) line = %s\n", converted);
+#endif
+
+
+ /* Paint the line. */
+ edit_draw(fileptr, converted, line, page_start);
+ free(converted);
+
+ if (!ISSET(SOFTWRAP)) {
+ if (page_start > 0)
+ mvwaddch(edit, line, 0, '$');
+ if (strlenpt(fileptr->data) > page_start + COLS)
+ mvwaddch(edit, line, COLS - 1, '$');
+ } else {
+ int full_length = strlenpt(fileptr->data);
+ for (index += COLS; index <= full_length && line < editwinrows; index += COLS) {
+ line++;
+#ifdef DEBUG
+ fprintf(stderr, "update_line(): Softwrap code, moving to %d index %lu\n", line, (unsigned long) index);
+#endif
+ blank_line(edit, line, 0, COLS);
+
+ /* Expand the line, replacing tabs with spaces, and control
+ * characters with their displayed forms. */
+ converted = display_string(fileptr->data, index, COLS, !ISSET(SOFTWRAP));
+#ifdef DEBUG
+ if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2)
+ fprintf(stderr, "update_line(): converted(2) line = %s\n", converted);
+#endif
+
+ /* Paint the line. */
+ edit_draw(fileptr, converted, line, index);
+ free(converted);
+ extralinesused++;
+ }
+ }
+ return extralinesused;
+}
+
+/* Return TRUE if we need an update after moving horizontally, and FALSE
+ * otherwise. We need one if the mark is on or if pww_save and
+ * placewewant are on different pages. */
+bool need_horizontal_update(size_t pww_save)
+{
+ return
+#ifndef NANO_TINY
+ openfile->mark_set ||
+#endif
+ get_page_start(pww_save) !=
+ get_page_start(openfile->placewewant);
+}
+
+/* Return TRUE if we need an update after moving vertically, and FALSE
+ * otherwise. We need one if the mark is on or if pww_save and
+ * placewewant are on different pages. */
+bool need_vertical_update(size_t pww_save)
+{
+ return
+#ifndef NANO_TINY
+ openfile->mark_set ||
+#endif
+ get_page_start(pww_save) !=
+ get_page_start(openfile->placewewant);
+}
+
+/* When edittop changes, try and figure out how many lines
+ * we really have to work with (i.e. set maxrows)
+ */
+void compute_maxrows(void)
+{
+ int n;
+ filestruct *foo = openfile->edittop;
+
+ if (!ISSET(SOFTWRAP)) {
+ maxrows = editwinrows;
+ return;
+ }
+
+ maxrows = 0;
+ for (n = 0; n < editwinrows && foo; n++) {
+ maxrows ++;
+ n += strlenpt(foo->data) / COLS;
+ foo = foo->next;
+ }
+
+ if (n < editwinrows)
+ maxrows += editwinrows - n;
+
+#ifdef DEBUG
+ fprintf(stderr, "compute_maxrows(): maxrows = %ld\n", maxrows);
+#endif
+}
+
+/* Scroll the edit window in the given direction and the given number
+ * of lines, and draw new lines on the blank lines left after the
+ * scrolling. direction is the direction to scroll, either UP_DIR or
+ * DOWN_DIR, and nlines is the number of lines to scroll. We change
+ * edittop, and assume that current and current_x are up to date. We
+ * also assume that scrollok(edit) is FALSE. */
+void edit_scroll(scroll_dir direction, ssize_t nlines)
+{
+ filestruct *foo;
+ ssize_t i, extracuzsoft = 0;
+ bool do_redraw = FALSE;
+
+ /* Don't bother scrolling less than one line. */
+ if (nlines < 1)
+ return;
+
+ if (need_vertical_update(0))
+ do_redraw = TRUE;
+
+
+ /* If using soft wrapping, we want to scroll down enough to display the entire next
+ line, if possible... */
+ if (ISSET(SOFTWRAP) && direction == DOWN_DIR) {
+#ifdef DEBUG
+ fprintf(stderr, "Softwrap: Entering check for extracuzsoft\n");
+#endif
+ for (i = maxrows, foo = openfile->edittop; foo && i > 0; i--, foo = foo->next)
+ ;
+
+ if (foo) {
+ extracuzsoft += strlenpt(foo->data) / COLS;
+#ifdef DEBUG
+ fprintf(stderr, "Setting extracuzsoft to %lu due to strlen %lu of line %lu\n", (unsigned long) extracuzsoft,
+ (unsigned long) strlenpt(foo->data), (unsigned long) foo->lineno);
+#endif
+
+ /* Now account for whether the edittop line itself is >COLS, if scrolling down */
+ for (foo = openfile->edittop; foo && extracuzsoft > 0; nlines++) {
+ extracuzsoft -= 1 + strlenpt(foo->data) / COLS;
+#ifdef DEBUG
+ fprintf(stderr, "Edittop adjustment, setting nlines to %lu\n", (unsigned long) nlines);
+#endif
+ if (foo == openfile->filebot)
+ break;
+ foo = foo->next;
+ }
+ }
+ }
+
+ /* Part 1: nlines is the number of lines we're going to scroll the
+ * text of the edit window. */
+
+ /* Move the top line of the edit window up or down (depending on the
+ * value of direction) nlines lines, or as many lines as we can if
+ * there are fewer than nlines lines available. */
+ for (i = nlines; i > 0; i--) {
+ if (direction == UP_DIR) {
+ if (openfile->edittop == openfile->fileage)
+ break;
+ openfile->edittop = openfile->edittop->prev;
+ } else {
+ if (openfile->edittop == openfile->filebot)
+ break;
+ openfile->edittop = openfile->edittop->next;
+ }
+ /* Don't over-scroll on long lines */
+ if (ISSET(SOFTWRAP)) {
+ ssize_t len = strlenpt(openfile->edittop->data) / COLS;
+ i -= len;
+ if (len > 0)
+ do_redraw = TRUE;
+ }
+ }
+
+ /* Limit nlines to the number of lines we could scroll. */
+ nlines -= i;
+
+ /* Don't bother scrolling zero lines or more than the number of
+ * lines in the edit window minus one; in both cases, get out, and
+ * call edit_refresh() beforehand if we need to. */
+ if (nlines == 0 || do_redraw || nlines >= editwinrows) {
+ if (do_redraw || nlines >= editwinrows)
+ edit_refresh_needed = TRUE;
+ return;
+ }
+
+ /* Scroll the text of the edit window up or down nlines lines,
+ * depending on the value of direction. */
+ scrollok(edit, TRUE);
+ wscrl(edit, (direction == UP_DIR) ? -nlines : nlines);
+ scrollok(edit, FALSE);
+
+ /* Part 2: nlines is the number of lines in the scrolled region of
+ * the edit window that we need to draw. */
+
+ /* If the top or bottom line of the file is now visible in the edit
+ * window, we need to draw the entire edit window. */
+ if ((direction == UP_DIR && openfile->edittop ==
+ openfile->fileage) || (direction == DOWN_DIR &&
+ openfile->edittop->lineno + editwinrows - 1 >=
+ openfile->filebot->lineno))
+ nlines = editwinrows;
+
+ /* If the scrolled region contains only one line, and the line
+ * before it is visible in the edit window, we need to draw it too.
+ * If the scrolled region contains more than one line, and the lines
+ * before and after the scrolled region are visible in the edit
+ * window, we need to draw them too. */
+ nlines += (nlines == 1) ? 1 : 2;
+
+ if (nlines > editwinrows)
+ nlines = editwinrows;
+
+ /* If we scrolled up, we're on the line before the scrolled
+ * region. */
+ foo = openfile->edittop;
+
+ /* If we scrolled down, move down to the line before the scrolled
+ * region. */
+ if (direction == DOWN_DIR) {
+ for (i = editwinrows - nlines; i > 0 && foo != NULL; i--)
+ foo = foo->next;
+ }
+
+ /* Draw new lines on any blank lines before or inside the scrolled
+ * region. If we scrolled down and we're on the top line, or if we
+ * scrolled up and we're on the bottom line, the line won't be
+ * blank, so we don't need to draw it unless the mark is on or we're
+ * not on the first page. */
+ for (i = nlines; i > 0 && foo != NULL; i--) {
+ if ((i == nlines && direction == DOWN_DIR) || (i == 1 &&
+ direction == UP_DIR)) {
+ if (do_redraw)
+ update_line(foo, (foo == openfile->current) ?
+ openfile->current_x : 0);
+ } else
+ update_line(foo, (foo == openfile->current) ?
+ openfile->current_x : 0);
+ foo = foo->next;
+ }
+}
+
+/* Update any lines between old_current and current that need to be
+ * updated. Use this if we've moved without changing any text. */
+void edit_redraw(filestruct *old_current, size_t pww_save)
+{
+ bool do_redraw = need_vertical_update(0) ||
+ need_vertical_update(pww_save);
+ filestruct *foo = NULL;
+
+ /* If either old_current or current is offscreen, scroll the edit
+ * window until it's onscreen and get out. */
+ if (old_current->lineno < openfile->edittop->lineno ||
+ old_current->lineno >= openfile->edittop->lineno +
+ maxrows || openfile->current->lineno <
+ openfile->edittop->lineno || openfile->current->lineno >=
+ openfile->edittop->lineno + maxrows) {
+
+#ifdef DEBUG
+ fprintf(stderr, "edit_redraw(): line %lu was offscreen, oldcurrent = %lu edittop = %lu", openfile->current->lineno,
+ old_current->lineno, openfile->edittop->lineno);
+#endif
+ filestruct *old_edittop = openfile->edittop;
+
+#ifndef NANO_TINY
+ /* If the mark is on, update all the lines between old_current
+ * and either the old first line or old last line (depending on
+ * whether we've scrolled up or down) of the edit window. */
+ if (openfile->mark_set) {
+ ssize_t old_lineno;
+
+ if (old_edittop->lineno < openfile->edittop->lineno)
+ old_lineno = old_edittop->lineno;
+ else
+ old_lineno = (old_edittop->lineno + maxrows <=
+ openfile->filebot->lineno) ?
+ old_edittop->lineno + editwinrows :
+ openfile->filebot->lineno;
+
+ foo = old_current;
+
+ while (foo->lineno != old_lineno) {
+ update_line(foo, 0);
+
+ foo = (foo->lineno > old_lineno) ? foo->prev :
+ foo->next;
+ }
+ }
+#endif /* !NANO_TINY */
+
+ /* Put edittop in range of current, get the difference in lines
+ * between the original edittop and the current edittop, and
+ * then restore the original edittop. */
+ edit_update(CENTER);
+
+ /* Update old_current if we're not on the same page as
+ * before. */
+ if (do_redraw)
+ update_line(old_current, 0);
+
+#ifndef NANO_TINY
+ /* If the mark is on, update all the lines between the old first
+ * line or old last line of the edit window (depending on
+ * whether we've scrolled up or down) and current. */
+ if (openfile->mark_set) {
+ while (foo->lineno != openfile->current->lineno) {
+ update_line(foo, 0);
+
+ foo = (foo->lineno > openfile->current->lineno) ?
+ foo->prev : foo->next;
+ }
+ }
+#endif /* !NANO_TINY */
+
+ return;
+ }
+
+ /* Update old_current and current if we're not on the same page as
+ * before. If the mark is on, update all the lines between
+ * old_current and current too. */
+ foo = old_current;
+
+ while (foo != openfile->current) {
+ if (do_redraw)
+ update_line(foo, 0);
+
+#ifndef NANO_TINY
+ if (!openfile->mark_set)
+#endif
+ break;
+
+#ifndef NANO_TINY
+ foo = (foo->lineno > openfile->current->lineno) ? foo->prev :
+ foo->next;
+#endif
+ }
+
+ if (do_redraw)
+ update_line(openfile->current, openfile->current_x);
+}
+
+/* Refresh the screen without changing the position of lines. Use this
+ * if we've moved and changed text. */
+void edit_refresh(void)
+{
+ filestruct *foo;
+ int nlines;
+
+ /* Figure out what maxrows should really be */
+ compute_maxrows();
+
+ if (openfile->current->lineno < openfile->edittop->lineno ||
+ openfile->current->lineno >= openfile->edittop->lineno +
+ maxrows) {
+
+#ifdef DEBUG
+ fprintf(stderr, "edit_refresh(): line = %d, edittop %d + maxrows %d\n", openfile->current->lineno, openfile->edittop->lineno, maxrows);
+#endif
+
+ /* Put the top line of the edit window in range of the current
+ * line. */
+ edit_update(CENTER);
+ }
+
+ foo = openfile->edittop;
+
+#ifdef DEBUG
+ fprintf(stderr, "edit_refresh(): edittop->lineno = %ld\n", (long)openfile->edittop->lineno);
+#endif
+
+ for (nlines = 0; nlines < editwinrows && foo != NULL; nlines++) {
+ nlines += update_line(foo, (foo == openfile->current) ?
+ openfile->current_x : 0);
+ foo = foo->next;
+ }
+
+ for (; nlines < editwinrows; nlines++)
+ blank_line(edit, nlines, 0, COLS);
+
+ reset_cursor();
+ wnoutrefresh(edit);
+}
+
+/* Move edittop to put it in range of current, keeping current in the
+ * same place. location determines how we move it: if it's CENTER, we
+ * center current, and if it's NONE, we put current current_y lines
+ * below edittop. */
+void edit_update(update_type location)
+{
+ filestruct *foo = openfile->current;
+ int goal;
+
+ /* If location is CENTER, we move edittop up (editwinrows / 2)
+ * lines. This puts current at the center of the screen. If
+ * location is NONE, we move edittop up current_y lines if current_y
+ * is in range of the screen, 0 lines if current_y is less than 0,
+ * or (editwinrows - 1) lines if current_y is greater than
+ * (editwinrows - 1). This puts current at the same place on the
+ * screen as before, or at the top or bottom of the screen if
+ * edittop is beyond either. */
+ if (location == CENTER)
+ goal = editwinrows / 2;
+ else {
+ goal = openfile->current_y;
+
+ /* Limit goal to (editwinrows - 1) lines maximum. */
+ if (goal > editwinrows - 1)
+ goal = editwinrows - 1;
+ }
+
+ for (; goal > 0 && foo->prev != NULL; goal--) {
+ foo = foo->prev;
+ if (ISSET(SOFTWRAP) && foo)
+ goal -= strlenpt(foo->data) / COLS;
+ }
+ openfile->edittop = foo;
+#ifdef DEBUG
+ fprintf(stderr, "edit_udpate(), setting edittop to lineno %d\n", openfile->edittop->lineno);
+#endif
+ compute_maxrows();
+ edit_refresh_needed = TRUE;
+}
+
+/* Unconditionally redraw the entire screen. */
+void total_redraw(void)
+{
+#ifdef USE_SLANG
+ /* Slang curses emulation brain damage, part 4: Slang doesn't define
+ * curscr. */
+ SLsmg_touch_screen();
+ SLsmg_refresh();
+#else
+ wrefresh(curscr);
+#endif
+}
+
+/* Unconditionally redraw the entire screen, and then refresh it using
+ * the current file. */
+void total_refresh(void)
+{
+ total_redraw();
+ titlebar(NULL);
+ edit_refresh();
+ bottombars(currmenu);
+}
+
+/* Display the main shortcut list on the last two rows of the bottom
+ * portion of the window. */
+void display_main_list(void)
+{
+ bottombars(MMAIN);
+}
+
+/* If constant is TRUE, we display the current cursor position only if
+ * disable_cursorpos is FALSE. Otherwise, we display it
+ * unconditionally and set disable_cursorpos to FALSE. If constant is
+ * TRUE and disable_cursorpos is TRUE, we also set disable_cursorpos to
+ * FALSE, so that we leave the current statusbar alone this time, and
+ * display the current cursor position next time. */
+void do_cursorpos(bool constant)
+{
+ filestruct *f;
+ char c;
+ size_t i, cur_xpt = xplustabs() + 1;
+ size_t cur_lenpt = strlenpt(openfile->current->data) + 1;
+ int linepct, colpct, charpct;
+
+ assert(openfile->fileage != NULL && openfile->current != NULL);
+
+ f = openfile->current->next;
+ c = openfile->current->data[openfile->current_x];
+
+ openfile->current->next = NULL;
+ openfile->current->data[openfile->current_x] = '\0';
+
+ i = get_totsize(openfile->fileage, openfile->current);
+
+ openfile->current->data[openfile->current_x] = c;
+ openfile->current->next = f;
+
+ if (constant && disable_cursorpos) {
+ disable_cursorpos = FALSE;
+ return;
+ }
+
+ /* Display the current cursor position on the statusbar, and set
+ * disable_cursorpos to FALSE. */
+ linepct = 100 * openfile->current->lineno /
+ openfile->filebot->lineno;
+ colpct = 100 * cur_xpt / cur_lenpt;
+ charpct = (openfile->totsize == 0) ? 0 : 100 * i /
+ openfile->totsize;
+
+ statusbar(
+ _("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"),
+ (long)openfile->current->lineno,
+ (long)openfile->filebot->lineno, linepct,
+ (unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct,
+ (unsigned long)i, (unsigned long)openfile->totsize, charpct);
+
+ disable_cursorpos = FALSE;
+}
+
+/* Unconditionally display the current cursor position. */
+void do_cursorpos_void(void)
+{
+ do_cursorpos(FALSE);
+}
+
+void enable_nodelay(void)
+{
+ nodelay_mode = TRUE;
+ nodelay(edit, TRUE);
+}
+
+void disable_nodelay(void)
+{
+ nodelay_mode = FALSE;
+ nodelay(edit, FALSE);
+}
+
+
+/* Highlight the current word being replaced or spell checked. We
+ * expect word to have tabs and control characters expanded. */
+void do_replace_highlight(bool highlight, const char *word)
+{
+ size_t y = xplustabs(), word_len = strlenpt(word);
+
+ y = get_page_start(y) + COLS - y;
+ /* Now y is the number of columns that we can display on this
+ * line. */
+
+ assert(y > 0);
+
+ if (word_len > y)
+ y--;
+
+ reset_cursor();
+ wnoutrefresh(edit);
+
+ if (highlight)
+ wattron(edit, reverse_attr);
+
+ /* This is so we can show zero-length matches. */
+ if (word_len == 0)
+ waddch(edit, ' ');
+ else
+ waddnstr(edit, word, actual_x(word, y));
+
+ if (word_len > y)
+ waddch(edit, '$');
+
+ if (highlight)
+ wattroff(edit, reverse_attr);
+}
+
+#ifdef NANO_EXTRA
+#define CREDIT_LEN 55
+#define XLCREDIT_LEN 8
+
+/* Easter egg: Display credits. Assume nodelay(edit) and scrollok(edit)
+ * are FALSE. */
+void do_credits(void)
+{
+ bool old_more_space = ISSET(MORE_SPACE);
+ bool old_no_help = ISSET(NO_HELP);
+ int kbinput = ERR, crpos = 0, xlpos = 0;
+ const char *credits[CREDIT_LEN] = {
+ NULL, /* "The nano text editor" */
+ NULL, /* "version" */
+ VERSION,
+ "",
+ NULL, /* "Brought to you by:" */
+ "Chris Allegretta",
+ "Jordi Mallach",
+ "Adam Rogoyski",
+ "Rob Siemborski",
+ "Rocco Corsi",
+ "David Lawrence Ramsey",
+ "David Benbennick",
+ "Mike Frysinger",
+ "Ken Tyler",
+ "Sven Guckes",
+ NULL, /* credits[15], handled below. */
+ "Pauli Virtanen",
+ "Daniele Medri",
+ "Clement Laforet",
+ "Tedi Heriyanto",
+ "Bill Soudan",
+ "Christian Weisgerber",
+ "Erik Andersen",
+ "Big Gaute",
+ "Joshua Jensen",
+ "Ryan Krebs",
+ "Albert Chin",
+ "",
+ NULL, /* "Special thanks to:" */
+ "Plattsburgh State University",
+ "Benet Laboratories",
+ "Amy Allegretta",
+ "Linda Young",
+ "Jeremy Robichaud",
+ "Richard Kolb II",
+ NULL, /* "The Free Software Foundation" */
+ "Linus Torvalds",
+ NULL, /* "For ncurses:" */
+ "Thomas Dickey",
+ "Pavel Curtis",
+ "Zeyd Ben-Halim",
+ "Eric S. Raymond",
+ NULL, /* "and anyone else we forgot..." */
+ NULL, /* "Thank you for using nano!" */
+ "",
+ "",
+ "",
+ "",
+ "(C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007",
+ "Free Software Foundation, Inc.",
+ "",
+ "",
+ "",
+ "",
+ "http://www.nano-editor.org/"
+ };
+
+ const char *xlcredits[XLCREDIT_LEN] = {
+ N_("The nano text editor"),
+ N_("version"),
+ N_("Brought to you by:"),
+ N_("Special thanks to:"),
+ N_("The Free Software Foundation"),
+ N_("For ncurses:"),
+ N_("and anyone else we forgot..."),
+ N_("Thank you for using nano!")
+ };
+
+ /* credits[15]: Make sure this name is displayed properly, since we
+ * can't dynamically assign it above, using Unicode 00F6 (Latin
+ * Small Letter O with Diaresis) if applicable. */
+ credits[15] =
+#ifdef ENABLE_UTF8
+ using_utf8() ? "Florian K\xC3\xB6nig" :
+#endif
+ "Florian K\xF6nig";
+
+ if (!old_more_space || !old_no_help) {
+ SET(MORE_SPACE);
+ SET(NO_HELP);
+ window_init();
+ }
+
+ curs_set(0);
+ nodelay(edit, TRUE);
+
+ blank_titlebar();
+ blank_topbar();
+ blank_edit();
+ blank_statusbar();
+ blank_bottombars();
+
+ wrefresh(topwin);
+ wrefresh(edit);
+ wrefresh(bottomwin);
+ napms(700);
+
+ for (crpos = 0; crpos < CREDIT_LEN + editwinrows / 2; crpos++) {
+ if ((kbinput = wgetch(edit)) != ERR)
+ break;
+
+ if (crpos < CREDIT_LEN) {
+ const char *what;
+ size_t start_x;
+
+ if (credits[crpos] == NULL) {
+ assert(0 <= xlpos && xlpos < XLCREDIT_LEN);
+
+ what = _(xlcredits[xlpos]);
+ xlpos++;
+ } else
+ what = credits[crpos];
+
+ start_x = COLS / 2 - strlenpt(what) / 2 - 1;
+ mvwaddstr(edit, editwinrows - 1 - (editwinrows % 2),
+ start_x, what);
+ }
+
+ wrefresh(edit);
+
+ if ((kbinput = wgetch(edit)) != ERR)
+ break;
+ napms(700);
+
+ scrollok(edit, TRUE);
+ wscrl(edit, 1);
+ scrollok(edit, FALSE);
+ wrefresh(edit);
+
+ if ((kbinput = wgetch(edit)) != ERR)
+ break;
+ napms(700);
+
+ scrollok(edit, TRUE);
+ wscrl(edit, 1);
+ scrollok(edit, FALSE);
+ wrefresh(edit);
+ }
+
+ if (kbinput != ERR)
+ ungetch(kbinput);
+
+ if (!old_more_space || !old_no_help) {
+ UNSET(MORE_SPACE);
+ UNSET(NO_HELP);
+ window_init();
+ }
+
+ curs_set(1);
+ nodelay(edit, FALSE);
+
+ total_refresh();
+}
+#endif /* NANO_EXTRA */