diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 30 | ||||
-rw-r--r-- | src/Makefile.in | 536 | ||||
-rw-r--r-- | src/browser.c | 1078 | ||||
-rw-r--r-- | src/chars.c | 963 | ||||
-rw-r--r-- | src/color.c | 431 | ||||
-rw-r--r-- | src/cut.c | 291 | ||||
-rw-r--r-- | src/files.c | 3041 | ||||
-rw-r--r-- | src/global.c | 1730 | ||||
-rw-r--r-- | src/help.c | 563 | ||||
-rw-r--r-- | src/move.c | 670 | ||||
-rw-r--r-- | src/nano.c | 2735 | ||||
-rw-r--r-- | src/nano.h | 794 | ||||
-rw-r--r-- | src/prompt.c | 1363 | ||||
-rw-r--r-- | src/proto.h | 863 | ||||
-rw-r--r-- | src/rcfile.c | 1305 | ||||
-rw-r--r-- | src/search.c | 1498 | ||||
-rw-r--r-- | src/text.c | 3079 | ||||
-rw-r--r-- | src/utils.c | 674 | ||||
-rw-r--r-- | src/winio.c | 3586 |
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("ereg); + 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("ereg, quotestr, REG_EXTENDED); + + if (quoterc == 0) { + /* We no longer need quotestr, just quotereg. */ + free(quotestr); + quotestr = NULL; + } else { + size_t size = regerror(quoterc, "ereg, NULL, 0); + + quoteerr = charalloc(size); + regerror(quoterc, "ereg, 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("ereg, 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("e_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 */ |