diff -Nru smenu-0.9.14/ChangeLog smenu-0.9.15/ChangeLog --- smenu-0.9.14/ChangeLog 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/ChangeLog 2019-03-30 14:08:14.000000000 +0000 @@ -1,4 +1,36 @@ -May 22 22:01 UTC 2018 - p.gen.progs@gmail.com +- Version 0.9.15 +* Bug fixes: + + An ending pattern search was not correctly highlighted + + An highlighting issue when hitting the HOME key during a search + in numbered mode has been fixed + + The PgUp/PgDn moves have been fixed and improved in the presence of + excluded words + + The height of the windows when a message is present is now correctly + calculated + + The SIGINT signal handling is working properly now + + A vt100 terminal is assumed when the environment variable TERM + is unset +* Improvements: + + The 'o' sub-option of the -D option has been enhanced + + The documentation has been improved and enhanced + + An example of a hierarchical menu interpreter has been added + + The message (title) appearance can now be changed with the -a option + + An empty line has been added after the message (title) for clarity + + The .spec file has been updated to support older SUSE/RH versions + + An issue opening /dev/tty is now clearly signaled + + The argument of the -n option is now optional and defaults to 0 + (full height) + + It is now possible to directly move the cursor to the start/end of + the current line +* Misc: + + The code has been split into several files + + The pull request #15 from sumbach/patch-1 has been merged + + The pull request #12 from jonnydubowsky/patch-1 has been merged + + More tests for the automatic regressions checking have been added + + UTF-8 string length processing has been optimized + + Help and usage messages are now printed on stdout + + A FAQ has been added + + Compiler warnings have been silenced - Version 0.9.14 * Rework the search system (major change) diff -Nru smenu-0.9.14/.clang-format smenu-0.9.15/.clang-format --- smenu-0.9.14/.clang-format 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/.clang-format 2019-03-30 14:08:14.000000000 +0000 @@ -14,7 +14,7 @@ AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: 'false' AllowShortLoopsOnASingleLine: 'false' -AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakBeforeMultilineStrings: 'false' BinPackArguments: 'true' BinPackParameters: 'true' BreakBeforeBraces: Allman diff -Nru smenu-0.9.14/configure smenu-0.9.15/configure --- smenu-0.9.14/configure 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/configure 2019-03-30 14:08:14.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for smenu 0.9.14. +# Generated by GNU Autoconf 2.69 for smenu 0.9.15. # # Report bugs to . # @@ -580,8 +580,8 @@ # Identity of this package. PACKAGE_NAME='smenu' PACKAGE_TARNAME='smenu' -PACKAGE_VERSION='0.9.14' -PACKAGE_STRING='smenu 0.9.14' +PACKAGE_VERSION='0.9.15' +PACKAGE_STRING='smenu 0.9.15' PACKAGE_BUGREPORT='p.gen.progs@gmail.com' PACKAGE_URL='' @@ -1275,7 +1275,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures smenu 0.9.14 to adapt to many kinds of systems. +\`configure' configures smenu 0.9.15 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1345,7 +1345,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of smenu 0.9.14:";; + short | recursive ) echo "Configuration of smenu 0.9.15:";; esac cat <<\_ACEOF @@ -1436,7 +1436,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -smenu configure 0.9.14 +smenu configure 0.9.15 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1859,7 +1859,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by smenu $as_me 0.9.14, which was +It was created by smenu $as_me 0.9.15, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2725,7 +2725,7 @@ # Define the identity of the package. PACKAGE='smenu' - VERSION='0.9.14' + VERSION='0.9.15' cat >>confdefs.h <<_ACEOF @@ -5618,7 +5618,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by smenu $as_me 0.9.14, which was +This file was extended by smenu $as_me 0.9.15, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -5684,7 +5684,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -smenu config.status 0.9.14 +smenu config.status 0.9.15 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru smenu-0.9.14/debian/changelog smenu-0.9.15/debian/changelog --- smenu-0.9.14/debian/changelog 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/changelog 2019-08-04 12:28:04.000000000 +0000 @@ -1,3 +1,14 @@ +smenu (0.9.15-1) unstable; urgency=medium + + * New upstream release. + * Switched to dh compat 12 (no changes needed). + * Bumped Standards-Version to 4.4.0 (no changes needed). + * Updated debian/copyright. + * debian/watch: quit using template. + * Added patch 0002-manpage-fix-macro-not-defined-warning.patch. + + -- Matteo Cypriani Sun, 04 Aug 2019 14:28:04 +0200 + smenu (0.9.14-1) unstable; urgency=medium * New upstream release. diff -Nru smenu-0.9.14/debian/compat smenu-0.9.15/debian/compat --- smenu-0.9.14/debian/compat 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/compat 2019-08-04 12:28:04.000000000 +0000 @@ -1 +1 @@ -11 +12 diff -Nru smenu-0.9.14/debian/control smenu-0.9.15/debian/control --- smenu-0.9.14/debian/control 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/control 2019-08-04 12:28:04.000000000 +0000 @@ -2,8 +2,8 @@ Section: utils Priority: optional Maintainer: Matteo Cypriani -Build-Depends: debhelper (>= 11), libncurses-dev -Standards-Version: 4.2.1 +Build-Depends: debhelper (>= 12), libncurses-dev +Standards-Version: 4.4.0 Homepage: https://github.com/p-gen/smenu Vcs-Git: https://salsa.debian.org/mcy-guest/smenu.git Vcs-Browser: https://salsa.debian.org/mcy-guest/smenu diff -Nru smenu-0.9.14/debian/copyright smenu-0.9.15/debian/copyright --- smenu-0.9.14/debian/copyright 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/copyright 2019-08-04 12:28:04.000000000 +0000 @@ -5,15 +5,15 @@ License: GPL-2 Files: * -Copyright: 2015-2018 Pierre Gentile +Copyright: 2015-2019 Pierre Gentile License: GPL-2 Files: smenu.spec.in -Copyright: 2015 SUSE LINUX GmbH, Nuernberg, Germany +Copyright: 2015, 2018 SUSE LINUX GmbH, Nuernberg, Germany License: GPL-2 Files: debian/* -Copyright: 2018 Matteo Cypriani +Copyright: 2018-2019 Matteo Cypriani License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff -Nru smenu-0.9.14/debian/.git-dpm smenu-0.9.15/debian/.git-dpm --- smenu-0.9.14/debian/.git-dpm 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/.git-dpm 2019-08-04 12:28:04.000000000 +0000 @@ -1,8 +1,8 @@ # see git-dpm(1) from git-dpm package -e692096ef32b2a3ffa066fe3a03ec7e22d58149d -e692096ef32b2a3ffa066fe3a03ec7e22d58149d -a0e32a91e7aa7acb5316a7b739ea7ff5142cd64d -a0e32a91e7aa7acb5316a7b739ea7ff5142cd64d -smenu_0.9.14.orig.tar.gz -370447c0f17f50e783c5a33ca2126d5e66ec58d2 -330681 +90958cb238ce3e908df6bd59ff02569501d1d06c +90958cb238ce3e908df6bd59ff02569501d1d06c +ce56278c6355340daa71a1850c56616dfb3c1c57 +ce56278c6355340daa71a1850c56616dfb3c1c57 +smenu_0.9.15.orig.tar.gz +645a3f5a0116469e5c9847f2b1d3fa4b47b6b155 +344139 diff -Nru smenu-0.9.14/debian/patches/0001-manpage-unconditionally-typo.patch smenu-0.9.15/debian/patches/0001-manpage-unconditionally-typo.patch --- smenu-0.9.14/debian/patches/0001-manpage-unconditionally-typo.patch 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/patches/0001-manpage-unconditionally-typo.patch 2019-08-04 12:28:04.000000000 +0000 @@ -1,4 +1,4 @@ -From e692096ef32b2a3ffa066fe3a03ec7e22d58149d Mon Sep 17 00:00:00 2001 +From 1a1a0cd0769db0c92feae7bdfb5168745aca82fc Mon Sep 17 00:00:00 2001 From: Matteo Cypriani Date: Mon, 1 Oct 2018 20:12:50 +0200 Subject: manpage: "unconditionally" typo @@ -8,10 +8,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smenu.1 b/smenu.1 -index 0982ca2..c5767d6 100644 +index 77db2ba..f530aa8 100644 --- a/smenu.1 +++ b/smenu.1 -@@ -878,7 +878,7 @@ any. +@@ -897,7 +897,7 @@ any. The allowed directives are: '\f(CBtrim\fP' which discads them if they form an empty word (only made of spaces and tabulations), '\f(CBcut\fP' diff -Nru smenu-0.9.14/debian/patches/0002-manpage-fix-macro-not-defined-warning.patch smenu-0.9.15/debian/patches/0002-manpage-fix-macro-not-defined-warning.patch --- smenu-0.9.14/debian/patches/0002-manpage-fix-macro-not-defined-warning.patch 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/debian/patches/0002-manpage-fix-macro-not-defined-warning.patch 2019-08-04 12:28:04.000000000 +0000 @@ -0,0 +1,25 @@ +From 90958cb238ce3e908df6bd59ff02569501d1d06c Mon Sep 17 00:00:00 2001 +From: Matteo Cypriani +Date: Sun, 4 Aug 2019 14:18:14 +0200 +Subject: manpage: fix 'macro not defined' warning + +Using tilted quotes instead of straight single quote. +This fixes both the warning, and the fact that this whole line did not +appear in the formatted man output. +--- + smenu.1 | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/smenu.1 b/smenu.1 +index f530aa8..d737fc3 100644 +--- a/smenu.1 ++++ b/smenu.1 +@@ -876,7 +876,7 @@ The default is \f(CBright\fP. + Here \fBy\fP is '\f(CBincluded\fP' (or one of its prefixes) + or '\f(CBall\fP' (or one of its prefixes) for the initial \fIp\fPadding of + the non numbered words. +-'\f(CBincluded\fP' means that only \fIincluded\fP word will be padded ++`\f(CBincluded\fP\' means that only \fIincluded\fP word will be padded + while '\f(CBall\fP' means pad \fIall\fP words. + The default is \f(CBall\fP. + . diff -Nru smenu-0.9.14/debian/patches/series smenu-0.9.15/debian/patches/series --- smenu-0.9.14/debian/patches/series 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/patches/series 2019-08-04 12:28:04.000000000 +0000 @@ -1 +1,2 @@ 0001-manpage-unconditionally-typo.patch +0002-manpage-fix-macro-not-defined-warning.patch diff -Nru smenu-0.9.14/debian/watch smenu-0.9.15/debian/watch --- smenu-0.9.14/debian/watch 2018-10-01 18:16:21.000000000 +0000 +++ smenu-0.9.15/debian/watch 2019-08-04 12:28:04.000000000 +0000 @@ -1,4 +1,4 @@ version=4 -opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%-$1.tar.gz%" \ +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%smenu-$1.tar.gz%" \ https://github.com/p-gen/smenu/tags \ (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate diff -Nru smenu-0.9.14/examples/lvm_menu/lvm_menu.sh smenu-0.9.15/examples/lvm_menu/lvm_menu.sh --- smenu-0.9.14/examples/lvm_menu/lvm_menu.sh 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/examples/lvm_menu/lvm_menu.sh 2019-03-30 14:08:14.000000000 +0000 @@ -1,4 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash + +# ==================== # +# Fatal error function # +# ==================== # +function error +{ + echo $* >&2 + exit 1 +} + +# Check for the presence of smenu +# """"""""""""""""""""""""""""""" +SMENU=$(which smenu 2>/dev/null) +[[ -x "$SMENU" ]] || error "smenu is not in the PATH or not executable." MENU+="\ 'Create' 'Delete' 'Extend' 'Shrink' @@ -19,7 +33,7 @@ echo MESSAGE="LVM management" - read REP <<< $(echo "$MENU" | ./menu.sh -i "$MESSAGE" "-Re1") + read REP <<< $(echo "$MENU" | ./menu.sh -p $SMENU -i "$MESSAGE" "-Re1") case $REP in ABORT) exit 1 ;; diff -Nru smenu-0.9.14/examples/lvm_menu/menu.sh smenu-0.9.15/examples/lvm_menu/menu.sh --- smenu-0.9.14/examples/lvm_menu/menu.sh 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/examples/lvm_menu/menu.sh 2019-03-30 14:08:14.000000000 +0000 @@ -1,9 +1,10 @@ -#!/bin/bash +#!/usr/bin/env bash # ################################################################ # # Frontend to smenu to display a nice menu # # # -# FLAGS: -i (optional) add some help in the title # +# FLAGS: -p The path of the smenu program # +# -i (optional) add some help in the title # # # # ARGS: $1 (required) must contain a string which will become # # the menu title # @@ -26,9 +27,12 @@ # Option processing # """"""""""""""""" -while getopts ih OPT 2>/dev/null +while getopts p:ih OPT 2>/dev/null do case ${OPT} in + p) SMENU=${OPTARG} + ;; + i) INFO=1 ;; @@ -53,7 +57,7 @@ # Menu display # """""""""""" -REP=$(../../smenu "$TITLE" \ +REP=$($SMENU "$TITLE" \ -s /Exit \ -q -d -M -n 30 -c -w \ -U "Exit menu" \ diff -Nru smenu-0.9.14/examples/lvm_menu/README smenu-0.9.15/examples/lvm_menu/README --- smenu-0.9.14/examples/lvm_menu/README 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/examples/lvm_menu/README 2019-03-30 14:08:14.000000000 +0000 @@ -1,5 +1,8 @@ This is an example of bash tool to build a customized LVM management menu. +First, make sure to make menu.sh and lvm_menu.sh executable: +(chmod +x menu.sh lvm_menu.sh). + menu.sh is used to create a generic menu with smenu lvm_menu.sh uses menu.sh to create a specialized menu to manage LVM objects diff -Nru smenu-0.9.14/examples/simple_menu/actions.sh smenu-0.9.15/examples/simple_menu/actions.sh --- smenu-0.9.14/examples/simple_menu/actions.sh 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/actions.sh 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +(( $# != 1 )) && exit 0 + +ACTION=$1 + +case $ACTION in + *) echo "Tag $ACTION selected." + sleep 1 + ;; +esac + +exit 0 diff -Nru smenu-0.9.14/examples/simple_menu/main.mnu smenu-0.9.15/examples/simple_menu/main.mnu --- smenu-0.9.14/examples/simple_menu/main.mnu 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/main.mnu 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ +# Directives +# .columns: Number of columns in the menu +# .centered: The menu will be centered +# .eraseafter: The menu window will be destroyed after the selection +# .title: The menu tittle + +.columns 2 +.title Simple menu + +ITEM1 First item +>sub1 First submenu +ITEM3 Third item +>sub2 Second submenu +=== +ITEM5 Fifth item +ITEM6 Sixth item +ITEM7 Seventh item +ITEM8 Eighth item +ITEM9 Ninth item +ITEM10 Tenth item +=== +EXIT Exit without selection diff -Nru smenu-0.9.14/examples/simple_menu/README smenu-0.9.15/examples/simple_menu/README --- smenu-0.9.14/examples/simple_menu/README 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/README 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,42 @@ +This example presents a simple hierarchical menu interpreter. + +Each menu and submenu are in a file suffixed by .mnu and is constituted +by directives and menu entries/preudo-entries. Each of them are in its +proper line. + +Comment lines are allowed and are introduced by a '#' in the first column + +The directives are: + +.columns: Set the number of columns in the menu +.centered: Tell if the menu must be centered +.eraseafter: Tell if the menu window must be destroyed after the selection + and the old cursor location restored. +.title: Set the menu tittle. + +The item lines has at least two fields: a tag and a menu item which will +be displayed. + +The tag is normally the returned value when an item is selected but may +also be part of a pseudo-entry. + +These special tags are: + +>xxx : Loads the submenu file xxx.mnu and interprets it. +< : Reload the previous menu file and interprets it +--- : Inserts an empty item in the menu +=== : Inserts an empty line in the menu (useful when there is more than + 1 column) +EXIT : Exits the menu without outputting anything. + +As usual, 'ENTER' triggers the selection and 'q' quits the menu without +outputting anything. + +To launch the demo, first, make sure to make simple_menu.sh executable +(chmod +x simple_menu.sh actions.sh) and enter: + +./simple_menu.sh main.mnu ./actions.sh + +The first argument is the main menu file and the second one is the path +of the program which will be called each time a selection is made. This +program will be given the selected tag as argument. diff -Nru smenu-0.9.14/examples/simple_menu/simple_menu.sh smenu-0.9.15/examples/simple_menu/simple_menu.sh --- smenu-0.9.14/examples/simple_menu/simple_menu.sh 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/simple_menu.sh 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,224 @@ +#!/usr/bin/env bash + +# Variables +# """"""""" +PROG=${0#*/} + +typeset -a MENU_STACK # A stack of MENUS to store the previously visited menus + +# Array of menu characteristics for caching purpose +# ''''''''''''''''''''''''''''''''''''''''''''''''' +typeset -A MENU_ARRAY +typeset -A COL_ARRAY +typeset -A CENTERING_ARRAY +typeset -A TITLE_ARRAY +typeset -A ERASE_ARRAY + +SEL= # The selection +SMENU= # The smenu path will be stored here + +# ============================ # +# Usage function, always fails # +# ============================ # +function usage +{ + echo "Usage: $PROG menu_file[.mnu] user_program," >&2 + echo " read the README for an example" >&2 + exit 1 +} + +# ==================== # +# Fatal error function # +# ==================== # +function error +{ + echo $* >&2 + exit 1 +} + +# The script expects exactly one argument (the filename of the root menu). +# """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +(( $# != 2 )) && usage + +USER_PROGRAM=$2 + +# ======================================================================= # +# Parse a level in the menu hierarchy and call process with the selection # +# (possibly empty). # +# ======================================================================= # +function process_menu +{ + TITLE=$'\n'"[ENTER: select, q: abort]" # Untitled by default + CENTERING="-M" # Is the menu centered in the screen ?, defaults to yes + ERASE="-d" # Destroy the selection window after use. Defaults to yes + + MENU_FILE=$1 + MENU= # Make sure the working area is empty + + # If the menu has already been seen, read its characteristics from the cache + # else construct them. + # """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + if [[ -z ${MENU_ARRAY[$MENU_FILE]} ]]; then + COL=1 + + # Parse the directives embedded in the menu file + # """""""""""""""""""""""""""""""""""""""""""""" + MENU_DIRECIVES=$(grep '^\.' $MENU_FILE) + + while read DIRECTIVE VALUE; do + case $DIRECTIVE in + .columns) # Number of columns of the menu + COL=$VALUE + (( COL < 1 || COL > 15 )) && error "$DIRECTIVE, bad value" + ;; + + .centered) # Is the menu centered? + [[ $VALUE == no ]] && CENTERING="" + ;; + + .eraseafter) # Will the space used by the menu be reclaimed? + [[ $VALUE == no ]] && ERASE="" + ;; + + .title) # The menu title + TITLE="$VALUE"$'\n'$TITLE + ;; + + *) + error "bad directive $DIRECTIVE" + ;; + esac + done <<< "$MENU_DIRECIVES" + + # Build the menu entries in the working area + # """""""""""""""""""""""""""""""""""""""""" + MENU_LINES=$(grep -v -e '^\.' -e '^#' -e '^[ \t]*$' $MENU_FILE) + + # The special tag "---" creates an empty entry (a hole in a column) + # The special tag "===" create an empty line + # The special tag "EXIT" permit to exit the menu + # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + + while read TAG VALUE; do + ITEMS_TO_ADD=1 # By default, only one iteration of the tag is taken + # into account + + (( ${#TAG} > 30 )) \ + && error "Menu tag \"${TAG}\" too long (max 30 characters)." + + [[ $TAG == --- ]] && VALUE=@@@ + [[ $TAG == === ]] && VALUE=@@@ && ITEMS_TO_ADD=COL + + [[ -z $VALUE ]] && error "Empty menu entry for $TAG" + [[ $TAG == EXIT ]] && TAG="@EXIT@xxxxxxxxxxxxxxxxxxxxxxxx00" + [[ $TAG == "<"* ]] && TAG=" 0 )); do + MENU+="'$TAG $FINAL_VALUE'"$'\n' + done + done <<< "$MENU_LINES" + + # Feed the cache + # """""""""""""" + MENU_ARRAY[$MENU_FILE]="$MENU" + COL_ARRAY[$MENU_FILE]=$COL + CENTERING_ARRAY[$MENU_FILE]=$CENTERING + TITLE_ARRAY[$MENU_FILE]=$TITLE + ERASE_ARRAY[$MENU_FILE]=$ERASE + else + # Read from the cache + # """"""""""""""""""" + MENU="${MENU_ARRAY[$MENU_FILE]}" + COL=${COL_ARRAY[$MENU_FILE]} + CENTERING=${CENTERING_ARRAY[$MENU_FILE]} + TITLE=${TITLE_ARRAY[$MENU_FILE]} + ERASE=${ERASE_ARRAY[$MENU_FILE]} + fi + + # Display the menu and get the selection + # """""""""""""""""""""""""""""""""""""" + SEL=$(echo "$MENU" | $SMENU \ + $CENTERING \ + $ERASE \ + -N \ + -F -D o:30 i:0 n:2 \ + -n \ + -a m:0/6 \ + -m "$TITLE" \ + -t $COL \ + -S'/@@@/ /' \ + -S'/^[^ ]+ //v' \ + -e @@@) + SEL=${SEL%% *} +} + +# Check for the presence of smenu +# """"""""""""""""""""""""""""""" +SMENU=$(which smenu 2>/dev/null) +[[ -x "$SMENU" ]] || error "smenu is not in the PATH or not executable." + +# Initialize the menu stack with the argument +# """"""""""""""""""""""""""""""""""""""""""" +MENU_STACK+=(${1%.mnu}.mnu) + +# And process the main menu +# """"""""""""""""""""""""" +process_menu ${1%.mnu}.mnu + +# According to the selection, navigate in the submenus or return +# the tag associated with the selected menu entry. +# """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +while true; do + if [[ $SEL == "<"* ]]; then + # Back to the previous menu + # ''''''''''''''''''''''''' + if (( ${#MENU_STACK[*]} == 1 )); then + process_menu ${MENU_STACK[-1]} + else + # Unstack the newly found submenu + # ''''''''''''''''''''''''''''''' + unset MENU_STACK[-1] + + # And generate the previous menu + # '''''''''''''''''''''''''''''' + process_menu ${MENU_STACK[-1]} + fi + + elif [[ $SEL == ">"* ]]; then + # Enter the selected submenu + # '''''''''''''''''''''''''' + SMENU_FILE=${SEL#>} + SMENU_FILE=${SMENU_FILE%.mnu}.mnu + [[ -f $SMENU_FILE ]] || error "The file $SMENU_FILE was not found/readable." + + # Stack the newly found submenu + # ''''''''''''''''''''''''''''' + MENU_STACK+=($SMENU_FILE) + + # And generate the submenu + # '''''''''''''''''''''''' + process_menu $SMENU_FILE + else + # An empty selection means than q or ^C has been hit + # '''''''''''''''''''''''''''''''''''''''''''''''''' + [[ -z $SEL ]] && exit 0 + + # Output the selected menu tag or exit the menu without outputting + # anything. + # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + [[ $SEL == @EXIT@* ]] && exit 0 + + # Lauch the user action which has the responsibility to act according + # to the tag passed as argument. + # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + $USER_PROGRAM $SEL + + # And re-generate the current menu + # '''''''''''''''''''''''''''''''' + process_menu ${MENU_STACK[-1]} + fi +done diff -Nru smenu-0.9.14/examples/simple_menu/sub1.mnu smenu-0.9.15/examples/simple_menu/sub1.mnu --- smenu-0.9.14/examples/simple_menu/sub1.mnu 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/sub1.mnu 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +.columns 1 +.title First submenu +SUB1ITEM1 First item of the first submenu +SUB1ITEM2 Second item of the first submenu +=== +< Previous Menu diff -Nru smenu-0.9.14/examples/simple_menu/sub2.mnu smenu-0.9.15/examples/simple_menu/sub2.mnu --- smenu-0.9.14/examples/simple_menu/sub2.mnu 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/examples/simple_menu/sub2.mnu 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +.columns 2 +.title Second submenu +SUB2ITEM1 First item of the second submenu +SUB2ITEM2 Second item of the second submenu +=== +< Previous Menu diff -Nru smenu-0.9.14/examples/yesno/README smenu-0.9.15/examples/yesno/README --- smenu-0.9.14/examples/yesno/README 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/examples/yesno/README 2019-03-30 14:08:14.000000000 +0000 @@ -1,6 +1,9 @@ With this example you will discover how easily you can ask a user to confirm a previous choice +First, make sure to make yesno.sh executable (chmod +x yesno.sh) and +enter: ./yesno.sh + The screenshot shows three use cases, one when just hitting 'Enter', one when moving the cursor to YES before hitting 'Enter' and the last one when just hitting 'q' diff -Nru smenu-0.9.14/examples/yesno/yesno.sh smenu-0.9.15/examples/yesno/yesno.sh --- smenu-0.9.14/examples/yesno/yesno.sh 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/examples/yesno/yesno.sh 2019-03-30 14:08:14.000000000 +0000 @@ -1,8 +1,22 @@ -#!/bin/bash +#!/usr/bin/env bash + +# ==================== # +# Fatal error function # +# ==================== # +function error +{ + echo $* >&2 + exit 1 +} + +# Check for the presence of smenu +# """"""""""""""""""""""""""""""" +SMENU=$(which smenu 2>/dev/null) +[[ -x "$SMENU" ]] || error "smenu is not in the PATH or not executable." RES=$( - ../../smenu -2 ^Y -1 ^N -3 ^C -s /^N -x cur 10 \ - -m "Please confirm your choice:" \ + $SMENU -2 ^Y -1 ^N -3 ^C -s /^N -x cur 10 \ + -m "Please confirm your choice:" \ <<< "YES NO CANCEL" ) diff -Nru smenu-0.9.14/FAQ smenu-0.9.15/FAQ --- smenu-0.9.14/FAQ 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/FAQ 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,21 @@ +Q: What is smenu? + +A: smenu is a selection tool which acts as a filter that takes words + from the standard input or a file and presents them on the screen in + various ways in a scrolling window. + + A cursor that you can easily move allows you to select one or more + of them. + + The selected words are printed on the standard output. +-------- +Q: Why smenu tells me: "The length of a word has reached the limit of + 512 characters." but there is no such word in my entry? + +A: There is a good chance you have an unbalanced quote, single or double, + somewhere. smenu uses quotation marks to be able to have spaces in + 'words', and these quotation marks that do not serve as delimiters + must be protected. + + You can use something like: sed -e "s/'/\\\'/g" -e 's/"/\\"/g' to + pre-process the input in such a case. diff -Nru smenu-0.9.14/fgetc.c smenu-0.9.15/fgetc.c --- smenu-0.9.14/fgetc.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/fgetc.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,42 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +/* ************************************************************************ */ +/* Custom fgetc/ungetc implementation able to unget more than one character */ +/* ************************************************************************ */ + +#include +#include "fgetc.h" + +enum +{ + GETC_BUFF_SIZE = 16 +}; + +static char getc_buffer[GETC_BUFF_SIZE] = { '\0' }; + +static long next_buffer_pos = 0; /* next free position in the getc buffer */ + +/* ====================================== */ +/* Get a (possibly pushed-back) character */ +/* ====================================== */ +int +my_fgetc(FILE * input) +{ + return (next_buffer_pos > 0) ? getc_buffer[--next_buffer_pos] : fgetc(input); +} + +/* ============================ */ +/* Push character back on input */ +/* ============================ */ +void +my_ungetc(int c) +{ + if (next_buffer_pos >= GETC_BUFF_SIZE) + fprintf(stderr, "Error: cannot push back more than %d characters\n", + GETC_BUFF_SIZE); + else + getc_buffer[next_buffer_pos++] = c; +} diff -Nru smenu-0.9.14/fgetc.h smenu-0.9.15/fgetc.h --- smenu-0.9.14/fgetc.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/fgetc.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,15 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef FGETC_H +#define FGETC_H + +int +my_fgetc(FILE * input); + +void +my_ungetc(int c); + +#endif diff -Nru smenu-0.9.14/getopt.c smenu-0.9.15/getopt.c --- smenu-0.9.14/getopt.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/getopt.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,211 @@ +/* egetopt.c -- Extended 'getopt'. + * + * A while back, a public-domain version of getopt() was posted to the + * net. A bit later, a gentleman by the name of Keith Bostic made some + * enhancements and reposted it. + * + * In recent weeks (i.e., early-to-mid 1988) there's been some + * heated discussion in comp.lang.c about the merits and drawbacks + * of getopt(), especially with regard to its handling of '?'. + * + * In light of this, I have taken Mr. Bostic's public-domain getopt() + * and have made some changes that I hope will be considered to be + * improvements. I call this routine 'egetopt' ("Extended getopt"). + * The default behavior of this routine is the same as that of getopt(), + * but it has some optional features that make it more useful. These + * options are controlled by the settings of some global variables. + * By not setting any of these extra global variables, you will have + * the same functionality as getopt(), which should satisfy those + * purists who believe getopt() is perfect and can never be improved. + * If, on the other hand, you are someone who isn't satisfied with the + * status quo, egetopt() may very well give you the added capabilities + * you want. + * + * Look at the enclosed README file for a description of egetopt()'s + * new features. + * + * The code was originally posted to the net as getopt.c by ... + * + * Keith Bostic + * ARPA: keith@seismo + * UUCP: seismo!keith + * + * Current version: added enhancements and comments, reformatted code. + * + * Lloyd Zusman + * Master Byte Software + * Los Gatos, California + * Internet: ljz@fx.com + * UUCP: ...!ames!fxgrp!ljz + * + * May, 1988 + * + * This version is slightly adapted for use by smenu. + */ + +#include +#include +#include +#include +#include "getopt.h" + +/* Here are all the pertinent global variables. */ +/* """""""""""""""""""""""""""""""""""""""""""" */ +int eopterr = 1; /* if true, output error message */ +int eoptind = 1; /* index into parent argv vector */ +int eoptopt; /* character checked for validity */ +int eoptbad = BADCH; /* character returned on error */ +int eoptchar = 0; /* character that begins returned option */ +int eoptneed = NEEDSEP; /* flag for mandatory argument */ +int eoptmaybe = MAYBESEP; /* flag for optional argument */ +int eopterrfd = ERRFD; /* file descriptor for error text */ +char * eoptarg = NULL; /* argument associated with option */ +char * eoptstart = START; /* list of characters that start options */ + +/* Here it is: */ +/* """"""""""" */ +int +egetopt(int nargc, char ** nargv, char * ostr) +{ + static char * place = EMSG; /* option letter processing */ + register char * oli; /* option letter list index */ + register char * osi = NULL; /* option start list index */ + + if (nargv == (char **)NULL) + return (EOF); + + if (nargc <= eoptind || nargv[eoptind] == NULL) + return (EOF); + + if (place == NULL) + place = EMSG; + + /* Update scanning pointer. */ + /* """""""""""""""""""""""" */ + if (*place == '\0') + { + place = nargv[eoptind]; + if (place == NULL) + return (EOF); + else if (*place == '\0') + return (EOF); + + osi = strchr(eoptstart, *place); + if (osi != NULL) + eoptchar = (int)*osi; + + if (eoptind >= nargc || osi == NULL || *++place == '\0') + return (EOF); + + /* Two adjacent, identical flag characters were found. */ + /* This takes care of "--", for example. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + if (*place == place[-1]) + { + ++eoptind; + return (EOF); + } + } + + /* If the option is a separator or the option isn't in the list, */ + /* we've got an error. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + eoptopt = (int)*place++; + oli = strchr(ostr, eoptopt); + if (eoptopt == eoptneed || eoptopt == (int)eoptmaybe || oli == NULL) + { + /* If we're at the end of the current argument, bump the */ + /* argument index. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (*place == '\0') + ++eoptind; + + TELL("Illegal option -- "); /* bye bye */ + + return (eoptbad); + } + + /* If there is no argument indicator, then we don't even try to */ + /* return an argument. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + ++oli; + if (*oli == '\0' || (*oli != (char)eoptneed && *oli != (char)eoptmaybe)) + { + /* If we're at the end of the current argument, bump the */ + /* argument index. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (*place == '\0') + ++eoptind; + + eoptarg = NULL; + } + /* If we're here, there's an argument indicator. It's handled */ + /* differently depending on whether it's a mandatory or an */ + /* optional argument. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + else + { + /* If there's no white space, use the rest of the */ + /* string as the argument. In this case, it doesn't */ + /* matter if the argument is mandatory or optional. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""" */ + if (*place != '\0') + eoptarg = place; + + /* If we're here, there's whitespace after the option. */ + /* */ + /* Is it a mandatory argument? If so, return the */ + /* next command-line argument if there is one. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + else if (*oli == (char)eoptneed) + { + /* If we're at the end of the argument list, there */ + /* isn't an argument and hence we have an error. */ + /* Otherwise, make 'eoptarg' point to the argument. */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + if (nargc <= ++eoptind) + { + place = EMSG; + TELL("Option requires an argument -- "); + + return (eoptbad); + } + else + eoptarg = nargv[eoptind]; + } + /* If we're here it must have been an optional argument. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + else + { + if (nargc <= ++eoptind) + { + place = EMSG; + eoptarg = NULL; + } + else + { + eoptarg = nargv[eoptind]; + if (eoptarg == NULL) + place = EMSG; + + /* If the next item begins with a flag */ + /* character, we treat it like a new */ + /* argument. This is accomplished by */ + /* decrementing 'eoptind' and returning */ + /* a null argument. */ + /* """""""""""""""""""""""""""""""""""" */ + else if (strchr(eoptstart, *eoptarg) != NULL) + { + --eoptind; + eoptarg = NULL; + } + } + } + place = EMSG; + ++eoptind; + } + + /* Return option letter. */ + /* """"""""""""""""""""" */ + return (eoptopt); +} diff -Nru smenu-0.9.14/getopt.h smenu-0.9.15/getopt.h --- smenu-0.9.14/getopt.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/getopt.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,35 @@ +#ifndef GETOPT_H +#define GETOPT_H + +/* None of these constants are referenced in the executable portion of */ +/* the code ... their sole purpose is to initialize global variables. */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +#define BADCH (int)'@' +#define NEEDSEP (int)':' +#define MAYBESEP (int)'%' +#define ERRFD 2 +#define EMSG "" +#define START "-" + +/* Conditionally print out an error message and return (depends on the */ +/* setting of 'opterr' and 'opterrfd'). Note that this version of */ +/* TELL() doesn't require the existence of stdio.h. */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +#define TELL(S) \ + { \ + if (eopterr && eopterrfd >= 0) \ + { \ + char option = (char)eoptopt; \ + if (write(eopterrfd, (S), strlen(S))) \ + ; \ + if (write(eopterrfd, &option, 1)) \ + ; \ + if (write(eopterrfd, "\n", 1)) \ + ; \ + } \ + } + +int +egetopt(int nargc, char ** nargv, char * ostr); + +#endif diff -Nru smenu-0.9.14/index.c smenu-0.9.15/index.c --- smenu-0.9.14/index.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/index.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,336 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +/* Ternary Search Tree and sorted array creation functions */ +/* Inspired by: https://www.cs.princeton.edu/~rs/strings/tstdemo.c */ +/* *************************************************************** */ + +#include +#include +#include +#include + +#include "xmalloc.h" +#include "list.h" +#include "index.h" + +/* List of words matching the current search */ +/* """"""""""""""""""""""""""""""""""""""""" */ +ll_t * tst_search_list; /* Must be initialized by ll_new() before use */ + +/* ====================================== */ +/* Ternary search tree insertion function */ +/* ====================================== */ +tst_node_t * +tst_insert(tst_node_t * p, wchar_t * w, void * data) +{ + if (p == NULL) + { + p = (tst_node_t *)xmalloc(sizeof(tst_node_t)); + p->splitchar = *w; + p->lokid = p->eqkid = p->hikid = NULL; + p->data = NULL; + } + + if (*w < p->splitchar) + p->lokid = tst_insert(p->lokid, w, data); + else if (*w == p->splitchar) + { + if (*w == L'\0') + { + p->data = data; + p->eqkid = NULL; + } + else + p->eqkid = tst_insert(p->eqkid, ++w, data); + } + else + p->hikid = tst_insert(p->hikid, w, data); + + return (p); +} + +/* ===================================== */ +/* Ternary search tree deletion function */ +/* user data area not cleaned */ +/* ===================================== */ +void +tst_cleanup(tst_node_t * p) +{ + if (p) + { + tst_cleanup(p->lokid); + if (p->splitchar) + tst_cleanup(p->eqkid); + tst_cleanup(p->hikid); + free(p); + } +} + +/* ========================================================== */ +/* Recursive traversal of a ternary tree. A callback function */ +/* is also called when a complete string is found */ +/* returns 1 if the callback function succeed (returned 1) at */ +/* least once */ +/* the first_call argument is for initializing the static */ +/* variable */ +/* ========================================================== */ +int +tst_traverse(tst_node_t * p, int (*callback)(void *), int first_call) +{ + static int rc; + + if (first_call) + rc = 0; + + if (!p) + return 0; + tst_traverse(p->lokid, callback, 0); + if (p->splitchar != L'\0') + tst_traverse(p->eqkid, callback, 0); + else + rc += (*callback)(p->data); + tst_traverse(p->hikid, callback, 0); + + return !!rc; +} + +/* ======================================================================= */ +/* Traverse the word tst looking for a wchar and build a list of pointers */ +/* containing all the sub-tst * nodes after these node potentially leading */ +/* to words containing the next wchar os the search string */ +/* ======================================================================= */ +int +tst_substring_traverse(tst_node_t * p, int (*callback)(void *), int first_call, + wchar_t w) +{ + static int rc; + + if (first_call) + rc = 0; + + if (!p) + return 0; + + if (p->splitchar == w) + { + ll_node_t * node; + sub_tst_t * sub_tst_data; + + node = tst_search_list->tail; + sub_tst_data = (sub_tst_t *)(node->data); + + if (p->eqkid) + insert_sorted_ptr(&(sub_tst_data->array), &(sub_tst_data->size), + &(sub_tst_data->count), p->eqkid); + + rc = 1; + } + + tst_substring_traverse(p->lokid, callback, 0, w); + if (p->splitchar != L'\0') + tst_substring_traverse(p->eqkid, callback, 0, w); + else if (callback != NULL) + rc += (*callback)(p->data); + tst_substring_traverse(p->hikid, callback, 0, w); + + return !!rc; +} + +/* ======================================================================= */ +/* Traverse the word tst looking for a wchar and build a list of pointers */ +/* containing all the sub-tst * nodes after these node potentially leading */ +/* to words containing the next wchar os the search string */ +/* ======================================================================= */ +int +tst_fuzzy_traverse(tst_node_t * p, int (*callback)(void *), int first_call, + wchar_t w) +{ + static int rc; + wchar_t w1s[2]; + wchar_t w2s[2]; + + w1s[1] = w2s[1] = L'\0'; + + if (first_call) + rc = 0; + + if (!p) + return 0; + + w1s[0] = p->splitchar; + w2s[0] = w; + + if (wcscasecmp(w1s, w2s) == 0) + { + ll_node_t * node; + sub_tst_t * sub_tst_data; + + node = tst_search_list->tail; + sub_tst_data = (sub_tst_t *)(node->data); + + if (p->eqkid != NULL) + insert_sorted_ptr(&(sub_tst_data->array), &(sub_tst_data->size), + &(sub_tst_data->count), p->eqkid); + + rc += 1; + } + + tst_fuzzy_traverse(p->lokid, callback, 0, w); + if (p->splitchar != L'\0') + tst_fuzzy_traverse(p->eqkid, callback, 0, w); + else if (callback != NULL) + rc += (*callback)(p->data); + tst_fuzzy_traverse(p->hikid, callback, 0, w); + + return !!rc; +} + +/* ==================================================================== */ +/* Search a complete string in a ternary tree starting from a root node */ +/* ==================================================================== */ +void * +tst_search(tst_node_t * root, wchar_t * w) +{ + tst_node_t * p; + + p = root; + + while (p) + { + if (*w < p->splitchar) + p = p->lokid; + else if (*w == p->splitchar) + { + if (*w++ == L'\0') + return p->data; + p = p->eqkid; + } + else + p = p->hikid; + } + + return NULL; +} + +/* =============================================================== */ +/* Search all strings beginning with the same prefix */ +/* the callback function will be applied to each of theses strings */ +/* returns NULL if no string matched the prefix */ +/* =============================================================== */ +void * +tst_prefix_search(tst_node_t * root, wchar_t * w, int (*callback)(void *)) +{ + tst_node_t * p = root; + size_t len = wcslen(w); + size_t rc; + + while (p) + { + if (*w < p->splitchar) + p = p->lokid; + else if (*w == p->splitchar) + { + len--; + if (*w++ == L'\0') + return p->data; + if (len == 0) + { + rc = tst_traverse(p->eqkid, callback, 1); + return (void *)(long)rc; + } + p = p->eqkid; + } + else + p = p->hikid; + } + + return NULL; +} + +/* ========================================================= */ +/* Insertion of an integer in a already sorted integer array */ +/* without duplications. */ +/* ========================================================= */ +void +insert_sorted_index(long ** array, long * size, long * nb, long value) +{ + long pos = *nb; + long left = 0, right = *nb, middle; + + if (*nb > 0) + { + /* bisection search */ + /* """""""""""""""" */ + while (left < right) + { + middle = (left + right) / 2; + if ((*array)[middle] == value) + return; /* Value already in array */ + + if (value < (*array)[middle]) + right = middle; + else + left = middle + 1; + } + pos = left; + } + + if (*nb == *size) + { + *size += 64; + *array = xrealloc(*array, *size * sizeof(long)); + } + + if (*nb > pos) + memmove((*array) + pos + 1, (*array) + pos, sizeof(value) * (*nb - pos)); + + (*nb)++; + + (*array)[pos] = value; +} + +/* ========================================================= */ +/* Insertion of an pointer in a already sorted pointer array */ +/* without duplications. */ +/* ========================================================= */ +void +insert_sorted_ptr(tst_node_t *** array, unsigned long long * size, + unsigned long long * nb, tst_node_t * ptr) +{ + unsigned long long pos = *nb; + unsigned long long left = 0, right = *nb, middle; + + if (*nb > 0) + { + /* bisection search */ + /* """""""""""""""" */ + while (left < right) + { + middle = (left + right) / 2; + if ((intptr_t)((*array)[middle]) == (intptr_t)ptr) + return; /* Value already in array */ + + if ((intptr_t)ptr < (intptr_t)((*array)[middle])) + right = middle; + else + left = middle + 1; + } + pos = left; + } + + if (*nb == *size) + { + *size += 64; + *array = xrealloc(*array, *size * sizeof(long)); + } + + if (*nb > pos) + memmove((*array) + pos + 1, (*array) + pos, sizeof(ptr) * (*nb - pos)); + + (*nb)++; + + (*array)[pos] = ptr; +} diff -Nru smenu-0.9.14/index.h smenu-0.9.15/index.h --- smenu-0.9.14/index.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/index.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,69 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef INDEX_H +#define INDEX_H + +/* *************************************** */ +/* Ternary Search Tree specific structures */ +/* *************************************** */ + +typedef struct tst_node_s tst_node_t; +typedef struct sub_tst_s sub_tst_t; + +#if 0 /* here for coherency but not used. */ +void tst_cleanup(tst_node_t * p); +#endif + +tst_node_t * +tst_insert(tst_node_t * p, wchar_t * w, void * data); + +void * +tst_prefix_search(tst_node_t * root, wchar_t * w, int (*callback)(void *)); + +void * +tst_search(tst_node_t * root, wchar_t * w); + +int +tst_traverse(tst_node_t * p, int (*callback)(void *), int first_call); + +int +tst_substring_traverse(tst_node_t * p, int (*callback)(void *), int first_call, + wchar_t w); +int +tst_fuzzy_traverse(tst_node_t * p, int (*callback)(void *), int first_call, + wchar_t w); + +sub_tst_t * +sub_tst_new(void); + +void +insert_sorted_index(long ** array, long * size, long * filled, long value); + +void +insert_sorted_ptr(tst_node_t *** array, unsigned long long * size, + unsigned long long * filled, tst_node_t * ptr); + +/* Ternary node structure */ +/* """""""""""""""""""""" */ +struct tst_node_s +{ + wchar_t splitchar; + + tst_node_t *lokid, *eqkid, *hikid; + void * data; +}; + +/* Structure to contain data and metadata attached to a fuzzy/substring. */ +/* search step. */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct sub_tst_s +{ + tst_node_t ** array; + unsigned long long size; + unsigned long long count; +}; + +#endif diff -Nru smenu-0.9.14/list.c smenu-0.9.15/list.c --- smenu-0.9.14/list.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/list.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,309 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +/* ********************************************************************* */ +/* Tiny linked list implementation. */ +/* */ +/* Each node contain a void pointer to some opaque data, these functions */ +/* will not try to allocate or free this data pointer. */ +/* */ +/* Also accessors are not provided, the user has to directly manipulate */ +/* the structure members (head, tail, len, data, prev, next) */ +/* ********************************************************************* */ + +#include +#include +#include +#include +#include + +#include "xmalloc.h" +#include "list.h" + +/* ======================== */ +/* Create a new linked list */ +/* ======================== */ +ll_t * +ll_new(void) +{ + ll_t * ret = xmalloc(sizeof(ll_t)); + ll_init(ret); + + return ret; +} + +/* ======================== */ +/* Initialize a linked list */ +/* ======================== */ +void +ll_init(ll_t * list) +{ + list->head = NULL; + list->tail = NULL; + list->len = 0; +} + +/* ==================================================== */ +/* Allocate the space for a new node in the linked list */ +/* ==================================================== */ +ll_node_t * +ll_new_node(void) +{ + ll_node_t * ret = xmalloc(sizeof(ll_node_t)); + + if (ret == NULL) + errno = ENOMEM; + + return ret; +} + +/* ==================================================================== */ +/* Append a new node filled with its data at the end of the linked list */ +/* The user is responsible for the memory management of the data */ +/* ==================================================================== */ +int +ll_append(ll_t * const list, void * const data) +{ + int ret = 1; + ll_node_t * node; + + if (list) + { + node = ll_new_node(); + if (node) + { + node->data = data; + node->next = NULL; + + node->prev = list->tail; + if (list->tail) + list->tail->next = node; + else + list->head = node; + + list->tail = node; + + ++list->len; + ret = 0; + } + } + + return ret; +} + +/* =================================================================== */ +/* Put a new node filled with its data at the beginning of the linked */ +/* list. The user is responsible for the memory management of the data */ +/* =================================================================== */ +int +ll_prepend(ll_t * const list, void * const data) +{ + int ret = 1; + ll_node_t * node; + + if (list) + { + node = ll_new_node(); + if (node) + { + node->data = data; + node->prev = NULL; + + node->next = list->head; + if (list->head) + list->head->prev = node; + else + list->tail = node; + + list->head = node; + + ++list->len; + ret = 0; + } + } + + return ret; +} + +/* ======================================================= */ +/* Insert a new node before the specified node in the list */ +/* TODO test it */ +/* ======================================================= */ +void +ll_insert_before(ll_t * const list, ll_node_t * node, void * const data) +{ + ll_node_t * new_node; + + if (list) + { + if (node->prev == NULL) + ll_prepend(list, data); + else + { + new_node = ll_new_node(); + if (new_node) + { + new_node->data = data; + new_node->next = node; + new_node->prev = node->prev; + node->prev->next = new_node; + + ++list->len; + } + } + } +} + +/* ====================================================== */ +/* Insert a new node after the specified node in the list */ +/* TODO test it */ +/* ====================================================== */ +void +ll_insert_after(ll_t * const list, ll_node_t * node, void * const data) +{ + ll_node_t * new_node; + + if (list) + { + if (node->next == NULL) + ll_append(list, data); + else + { + new_node = ll_new_node(); + if (new_node) + { + new_node->data = data; + new_node->prev = node; + new_node->next = node->next; + node->next->prev = new_node; + + ++list->len; + } + } + } +} + +/* ====================================================== */ +/* Partition code for the quicksort function */ +/* Based on code found here: */ +/* http://www.geeksforgeeks.org/quicksort-for-linked-list */ +/* ====================================================== */ +ll_node_t * +ll_partition(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), + void (*swap)(void *, void *)) +{ + /* Considers last element as pivot, places the pivot element at its */ + /* correct position in sorted array, and places all smaller (smaller than */ + /* pivot) to left of pivot and all greater elements to right of pivot */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + + /* Set pivot as h element */ + /* """""""""""""""""""""" */ + void * x = h->data; + + ll_node_t * i = l->prev; + ll_node_t * j; + + for (j = l; j != h; j = j->next) + { + if (comp(j->data, x) < 1) + { + i = (i == NULL) ? l : i->next; + + swap(i->data, j->data); + } + } + + i = (i == NULL) ? l : i->next; + swap(i->data, h->data); + + return i; +} + +/* ======================================================= */ +/* A recursive implementation of quicksort for linked list */ +/* ======================================================= */ +void +ll_quicksort(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), + void (*swap)(void * a, void *)) +{ + if (h != NULL && l != h && l != h->next) + { + ll_node_t * p = ll_partition(l, h, comp, swap); + ll_quicksort(l, p->prev, comp, swap); + ll_quicksort(p->next, h, comp, swap); + } +} + +/* =========================== */ +/* A linked list sort function */ +/* =========================== */ +void +ll_sort(ll_t * list, int (*comp)(void *, void *), + void (*swap)(void * a, void *)) +{ + /* Call the recursive ll_quicksort function */ + /* """""""""""""""""""""""""""""""""""""""" */ + ll_quicksort(list->head, list->tail, comp, swap); +} + +/* ================================ */ +/* Remove a node from a linked list */ +/* ================================ */ +int +ll_delete(ll_t * const list, ll_node_t * node) +{ + if (list->head == list->tail) + { + if (list->head != NULL) + list->head = list->tail = NULL; + else + return 0; + } + else if (node->prev == NULL) + { + list->head = node->next; + list->head->prev = NULL; + } + else if (node->next == NULL) + { + list->tail = node->prev; + list->tail->next = NULL; + } + else + { + node->next->prev = node->prev; + node->prev->next = node->next; + } + + free(node); + + --list->len; + + return 1; +} + +/* =========================================================================*/ +/* Find a node in the list containing data. Return the node pointer or NULL */ +/* if not found. */ +/* A comparison function must be provided to compare a and b (strcmp like). */ +/* =========================================================================*/ +ll_node_t * +ll_find(ll_t * const list, void * const data, + int (*cmpfunc)(const void * a, const void * b)) +{ + ll_node_t * node; + + if (NULL == (node = list->head)) + return NULL; + + do + { + if (0 == cmpfunc(node->data, data)) + return node; + } while (NULL != (node = node->next)); + + return NULL; +} diff -Nru smenu-0.9.14/list.h smenu-0.9.15/list.h --- smenu-0.9.14/list.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/list.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,73 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef LIST_H +#define LIST_H + +typedef struct ll_node_s ll_node_t; +typedef struct ll_s ll_t; + +/* ******************************* */ +/* Linked list specific structures */ +/* ******************************* */ + +/* Linked list node structure */ +/* """""""""""""""""""""""""" */ +struct ll_node_s +{ + void * data; + struct ll_node_s * next; + struct ll_node_s * prev; +}; + +/* Linked List structure */ +/* """"""""""""""""""""" */ +struct ll_s +{ + ll_node_t * head; + ll_node_t * tail; + long len; +}; + +int +ll_append(ll_t * const list, void * const data); + +int +ll_prepend(ll_t * const list, void * const data); + +void +ll_insert_before(ll_t * const list, ll_node_t * node, void * const data); + +void +ll_insert_after(ll_t * const list, ll_node_t * node, void * const data); + +ll_node_t * +ll_partition(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), + void (*swap)(void *, void *)); + +void +ll_quicksort(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), + void (*swap)(void * a, void *)); + +void +ll_sort(ll_t * list, int (*comp)(void *, void *), + void (*swap)(void * a, void *)); + +int +ll_delete(ll_t * const list, ll_node_t * node); + +ll_node_t * +ll_find(ll_t * const, void * const, int (*)(const void *, const void *)); + +void +ll_init(ll_t * list); + +ll_node_t * +ll_new_node(void); + +ll_t * +ll_new(void); + +#endif diff -Nru smenu-0.9.14/Makefile.am smenu-0.9.15/Makefile.am --- smenu-0.9.14/Makefile.am 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/Makefile.am 2019-03-30 14:08:14.000000000 +0000 @@ -1,8 +1,11 @@ bin_PROGRAMS = smenu -smenu_SOURCES = smenu.c +smenu_SOURCES = smenu.c smenu.h list.c list.h xmalloc.c xmalloc.h \ + index.c index.h utf8.c utf8.h fgetc.c fgetc.h \ + utils.c utils.h getopt.c getopt.h usage.c usage.h dist_man_MANS = smenu.1 -EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \ - COPYRIGHT LICENSE.rst README.rst examples build-aux tests +EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \ + COPYRIGHT LICENSE.rst README.rst examples build-aux tests \ + FAQ dist-hook: @chmod u+rw $(distdir)/tests; \ diff -Nru smenu-0.9.14/Makefile.in smenu-0.9.15/Makefile.in --- smenu-0.9.14/Makefile.in 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/Makefile.in 2019-03-30 14:08:14.000000000 +0000 @@ -102,7 +102,9 @@ CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" PROGRAMS = $(bin_PROGRAMS) -am_smenu_OBJECTS = smenu.$(OBJEXT) +am_smenu_OBJECTS = smenu.$(OBJEXT) list.$(OBJEXT) xmalloc.$(OBJEXT) \ + index.$(OBJEXT) utf8.$(OBJEXT) fgetc.$(OBJEXT) utils.$(OBJEXT) \ + getopt.$(OBJEXT) usage.$(OBJEXT) smenu_OBJECTS = $(am_smenu_OBJECTS) smenu_LDADD = $(LDADD) AM_V_P = $(am__v_P_@AM_V@) @@ -305,10 +307,14 @@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -smenu_SOURCES = smenu.c +smenu_SOURCES = smenu.c smenu.h list.c list.h xmalloc.c xmalloc.h \ + index.c index.h utf8.c utf8.h fgetc.c fgetc.h \ + utils.c utils.h getopt.c getopt.h usage.c usage.h + dist_man_MANS = smenu.1 -EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \ - COPYRIGHT LICENSE.rst README.rst examples build-aux tests +EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \ + COPYRIGHT LICENSE.rst README.rst examples build-aux tests \ + FAQ all: config.h $(MAKE) $(AM_MAKEFLAGS) all-am @@ -419,7 +425,15 @@ distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fgetc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/index.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/usage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xmalloc.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff -Nru smenu-0.9.14/README.rst smenu-0.9.15/README.rst --- smenu-0.9.14/README.rst 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/README.rst 2019-03-30 14:08:14.000000000 +0000 @@ -4,28 +4,31 @@ =========== **smenu** is a selection filter just like ``sed`` is an editing filter. -This simple tool reads words from the standard input, presents them in -a cool interactive window after the current line on the terminal and writes -the selected word, if any, on the standard output. +This tool takes words from standard input or from a file and presents +them on the screen in different layouts in a scrolling window. +A cursor that you can easily move lets you select one or more of them. + +Note that the screen is not previously cleared when the scrolling window +of **smenu** is displayed. + +I tried to make its use as simple as possible. It supports the ``UTF-8`` +encoding and should work on all terminals managed by the ``terminfo`` +database. -After having unsuccessfully searched the NET for what I wanted, I -decided to try to write my own. - -I have tried hard to made its usage as simple as possible. It should -work, even when using an old ``vt100`` terminal and is ``UTF-8`` aware. +Please use the included man page to learn more about this little program. The wiki (https://github.com/p-gen/smenu/wiki) contains screenshots and -animations that detail some of the concepts and features of smenu. +animations that detail some of the concepts and features of **smenu**. How to build it? ================ -**smenu** can be built on every system where a working ``terminfo`` -development platform is available. This includes every Unix and Unix -like systems I am aware of. +**smenu** can be built on any system on which a working ``terminfo`` +development platform is available. This includes every Unix and +Unix-like system I am aware of. Please use the provided ``build.sh`` to build the executable. This script accepts the same arguments as ``configure``, type ``build.sh ---help`` the see them. +--help`` to see them. The script ``autogen.sh`` is also provided if you need to generate a new ``configure`` script from ``configure.ac`` and ``Makefile.am``. The @@ -72,10 +75,6 @@ After having moved the cursor to ``" 136 kB"`` and ended the program with ````, the shell variable R should contain: ``" 136 kB"``. -.. raw:: pdf - - PageBreak - Unix example. ------------- The following command, which is Unix brand agnostic, should give you a @@ -112,23 +111,19 @@ **IMPORTANT** the testing system has some dependencies, please read the ``test/README.rst`` before going further. -**WARNING** running all the test by running ``./tests.sh`` in the +**WARNING** running all the tests by running ``./tests.sh`` in the ``tests`` directory will take some time (around 15 min for now). **NOTE** on some systems like \*BSD some tests may fail. This can be explained by differences in posix/libc/... implementations. This can -notably occur when some specific regular expressions or uncommon UTF-8 +notably occur when some specific regular expressions or uncommon ``UTF-8`` byte sequences are used. If a test fails for unknown reason, then please send me its directory name and the relevant ``.bad`` file. If you are hit by a bug that no test covers, then you can create a new -test in the ``tests`` directory in a existing or new directory, read the +test in the ``tests`` directory in an existing or new directory: read the ``tests/README.rst`` file, use an existing test as model, create an ``.in`` file and a ``.tst`` file and send them to me as well as the produced files. - -Interested? ------------ -Please use the included man page to learn more about this little program. diff -Nru smenu-0.9.14/smenu.1 smenu-0.9.15/smenu.1 --- smenu-0.9.14/smenu.1 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/smenu.1 2019-03-30 14:08:14.000000000 +0000 @@ -5,10 +5,11 @@ .SH SYNOPSIS .nf \f(CRsmenu [\fB-h\fP|\fB-?\fP] [\fB-f\fP \fIconfiguration_file\fP] \\ - [\fB-n\fP \fIlines\fP] [\fB-t\fP [\fIcols\fP]] [\fB-k\fP] \\ + [\fB-n\fP [\fIlines\fP]] [\fB-t\fP [\fIcols\fP]] [\fB-k\fP] \\ [\fB-s\fP \fIpattern\fP] [\fB-m\fP \fImessage\fP] [\fB-w\fP] \\ [\fB-d\fP] [\fB-M\fP] [\fB-c\fP] [\fB-l\fP] [\fB-r\fP] [\fB-b\fP] \\ - [\fB-a\fP (i|e|c|b|s|t|ct|sf|st|mf|mt|sfe|ste|mfe|mte|da):\fIATTR\fP]... \\ + [\fB-a\fP \ +(i|e|c|b|si|m|t|ct|sf|st|mf|mt|sfe|ste|mfe|mte|da):\fIATTR\fP]... \\ [\fB-i\fP \fIregex\fP] [\fB-e\fP \fIregex\fP] \\ [\fB-C\fP \ [\fIi\fP|\fIe\fP]] \\ @@ -33,7 +34,7 @@ ::= \fIcol1\fP[-\fIcol2\fP],...|\fI\fP,... ::= \fIcol1\fP[-\fIcol2\fP],...|\fI\fP,... ::= [l|r:]|[a:left|right]|[p:included|all| - [w:]|[f:yes|no]|[o:]|[n:]| + [w:]|[f:yes|no]|[o:[+]]|[n:]| [i:]|[d:]|[s:]|[h:trim|cut|keep] ::= [fg][/bg][,style] ::= \fB\fIregex\fB\fR @@ -106,20 +107,26 @@ .TS tab(@); l l. -\(<-, h@Previous word -\(ua, k@Previous line -PgUp, K@Previous pages -Home@First word of the window -CTRL+Home, SHIFT+Home, CTRL+k@First word -.sp -\(->, l@Next Word -\(da, j@Next line -PgDn, J@Next pages -End@Last word of the window -CTRL+End, SHIFT+End, CTRL+j@Last word +\fB\(<-\fP, \fBh\fP@Previous word +\fBCTRL\ \(<-\fP, \fBH\fP@Start of line +\fB\(ua\fP, \fBk\fP@Previous line +\fBPgUp\fP, \fBK\fP@Previous page +\fBHOME\fP@First word of the window +\fBCTRL\ HOME\fP, \fBSHIFT\ HOME\fP, \fBCTRL\ K\fP@First word + +\fB\(->\fP, \fBl\fP@Next word +\fBCTRL\ \(->\fP, \fBL\fP@End of line +\fB\(da\fP, \fBj\fP@Next line +\fBPgDn\fP, \fBJ\fP@Next page +\fBEND\fP@Last word of the window +\fBCTRL\ END\fP, \fBSHIFT\ END\fP, \fBCTRL\ J\fP@Last word .TE -If the \fB-N\fP, \fB-U\fP or \fB-F\fP are used then it is possible to +\fBCTRL\ \(<-\fP/\fBH\fP (resp. \fBCTRL\ \(->\fP/\fBL\fP) places the cursor +so that a maximum number of words (selectable or not) are visible to +the left (reps. right) side of the window. + +If \fB-N\fP, \fB-U\fP or \fB-F\fP are used, then it becomes possible to directly access a word by entering its number. The numbering created using these option is done \fBbefore\fP any words substitution done using \fB-S\fP, \fB-I\fP or \fB-E\fP. @@ -129,7 +136,7 @@ removing it (substituted by nothing) afterward using \fB-I\fP by example. \fB-E\fP gives another way to do that, see below or more. -.SS "Searching for a word" +.SS "Searching for words" A word can be searched using different algorithms: \fIprefix\fP, \fIsubstring\fP of \fIfuzzy\fP. .TP @@ -140,8 +147,8 @@ The sequence of characters entered must match a substring in a word. .TP \fIfuzzy\fP (keys \fB~\fP or \fB*\fP): -All the character in the entered sequence must appear in the same order -in a word must not necessarily be consecutive. +All the characters in the entered sequence must appear in the same order +in a word, but need not be consecutive. The case is also ignored. @@ -158,7 +165,7 @@ The erroneous symbols will \fInot\fP be inserted in the search buffer. -By example: if the word \fBabcdef\fP is present in the standard input, +For example: if the word \fBabcdef\fP is present in the standard input, then entering \f(CBabxcdye\fP puts \fBabcdef\fP in the search buffer and the word is added to the list of matching words and displayed with an error attribute (in red by default). @@ -170,20 +177,20 @@ added in (or removed from) the search buffer. The display is refreshed after each change in this buffer. .PP -The \fB/\fP key can also be used instead of any of these keys. By default -is is programmed to do a \fIfuzzy\fP search but this can be altered by -using the command line option (\fB-/\fP) or by tuning a configuration -file, see below. +The slash key (fB/\fP) can also be used instead of any of these keys. +By default it is is programmed to do a \fIfuzzy\fP search but this can +be changed by using the command line option (\fB-/\fP) or by tuning +a configuration file, see below. .PP All the words matching the current search buffer are enhanced: The characters present in the current search buffer are highlighted in one way and the other characters in another way. Both of these highlighting methods are configurable. .PP -Typically, if the user has entered the search sequence: \fBo\fP, \fBs\fP, -then the matching word "words" will be displayed as -\fBw\fP\fIo\fP\fBrd\fP\fIs\fP when the \fIfuzzy\fP algorithm is in use -depending of the display attributes configured. +If the user has entered the search sequence: \fBo\fP, \fBs\fP, then the +matching word "words" will be displayed as \fBw\fP\fIo\fP\fBrd\fP\fIs\fP +when the \fIfuzzy\fP algorithm is in use depending of the display +attributes configured. .PP \fBESC\fP can be used anytime to abort the current search session. \fBENTER\fP and all cursor moves also terminate the search @@ -203,19 +210,18 @@ \fBs\fP means next \fPs\fPubstring match in this context while \fBn\fP just means \fBn\fPext match. .PP -If the user hits the \fBHome\fP or \fBEnd\fP key during a search session +If the user hits the \fBHOME\fP or \fBEND\fP key during a search session then the list of matching words is reduced to the words starting (respectively) ending with the current search pattern and the window is refreshed. -For those who consider \fBHome\fP and \fBEnd\fP as non-intuitive, -the \fBCTRL-A\fP and \fBCtrl-Z\fP keys are also available in search mode +For those who consider \fBHOME\fP and \fBEND\fP as non-intuitive, +the \fBCTRL\ A\fP and \fBCTRL\ Z\fP keys are also available in search mode as an alternative. - This behaviour is persistent until the user hit the \fBESC\fP or \fBENTER\fP key. -By example, if the search pattern in substring mode is \f(CBsh\fP and -the user hits \fBEnd\fP, then only the words \fIending\fP with \f(CBsh\fP +For example, if the search pattern in substring mode is \f(CBsh\fP and +the user hits \fBEND\fP, then only the words \fIending\fP with \f(CBsh\fP will be added in the searched word list and enhanced. Note that when a matching word is selected, its enhanced characters only @@ -251,25 +257,27 @@ @@search @@session _ -\(<-@Previous word@Yes -\(ua@Previous line@Yes -PgUp@Previous page@Yes -CTRL+Home, SHIFT+Home, CTRL+k@First word@Yes - -\(->@Next word@Yes -\(da@Next line@Yes -PgDn@Next pages@Yes -CTRL+End, SHIFT+End, CTRL+j@Last word@Yes +\fB\(<-\fP@Previous word@Yes +\fB\(ua\fP@Previous line@Yes +\fBCTRL\ \(<-\fP@Start of line@Yes +\fBPgUp\fP@Previous page@Yes +\fBCTRL\ HOME\fP, \fBSHIFT\ HOME\fP, \fBCTRL\ K\fP@First word@Yes + +\fB\(->\fP@Next word@Yes +\fB\(da\fP@Next line@Yes +\fBCTRL\ \(->\fP@END of line@Yes +\fBPgDn\fP@Next pages@Yes +\fBCTRL\ END\fP, \fBSHIFT\ END\fP, \fBCTRL\ J\fP@Last word@Yes -Home,CTRL-A@T{ +\fBHOME\fP, \fBCTRL\ A\fP@T{ Only keep the words starting with the search pattern T}@No -End,CTRL-Z@T{ +\fBEND\fP, \fBCTRL\ Z\fP@T{ Only keep the words ending with the search pattern T}@No -Ins@Tag word@No -Del@Untag word@No +\fBINS\fP@Tag word@No +\fBDEL\fP@Untag word@No _ .TE .PP @@ -282,7 +290,7 @@ search mode otherwise it exits from this mode and does nothing more. If you want to be able to select a word \fIeven\fP when in search mode, use the \fB-r\fP option to change this behavior. -.SS "Tagging (multi-selections)" +.SS "Tagging (multiple selections)" When the tagging is activated by using the command line \fB-T\fP or \fB-P\fP option, then the keys \fBt\fP, \fBT\fP, \fBINS\fP and \fBDEL\fP can be used to tag/untag some words. @@ -389,6 +397,7 @@ cursor=0/2 ; cursor attributes cursor_on_tag=0/2,u ; cursor on tag attributes shift=6,b ; shift symbol attributes + message=0/3 ; message (title) attributes bar = 7/4,b ; scroll bar attributes search_field = 0/6 ; search field attributes search_text = 7,bu ; search text attributes @@ -464,19 +473,23 @@ Displays a long (\fB-h\fP) or short (\fB-?\fP) help message and exits. .IP "\fB-f\fP \fIconfiguration_file\fB" This option gives the possibility to select an alternative configuration -file. If the given file doesn't exist or is not readable then the -default values will be used. +file. +If the given file doesn't exist or is not readable then the default +values will be used. The \fB.smenu\fP files in the user's home directory and in the current directory, if present, will be ignored when this option is used. -.IP "\fB-n\fP \fIlines\fB" +.IP "\fB-n\fP \fI[lines\fB]" Gives the maximum number of lines in the scrolling selection window. -By default five lines at most are displayed and the other ones, if -any, need you to scroll the window. -The special value \fI0\fP sets this number to match the number of lines -in the terminal (minus the lines taken by the message if any). +If \fB-n\fP is not present the number of lines will be set to \fI5\fP. + +If \fB-n\fP is present without argument, then the height of the terminal will +be used to determine the number of lines. This remains true even if the terminal is resized. + +If \fB-n\fP is present with a numerical argument, this value will be used +to determine the number of lines. .IP "\fB-t\fP [\fIcolumns\fP]" This option sets the tabulation mode and, if a number is specified, attents to set the number of displayed columns to that number. @@ -496,8 +509,8 @@ .IP \fB-v\fP By default, when searching, an alarm is produced by the terminal when the user enters a character or makes a move which lead to no result or -to an error condition. This argument make this beep visual by briefly -showing the cursor. +to an error condition. +This argument make this beep visual by briefly showing the cursor. .IP "\fB-s\fP \fIpattern\fP" Place the cursor on the first word corresponding to the specified pattern. @@ -600,6 +613,8 @@ scroll bar. .IP \fIs\fP shift indicator. +.IP \fIm\fP +message (title). .IP \fIt\fP tagged words. .IP \fIct\fP @@ -840,26 +855,30 @@ .TP \f(CBl\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) Here \fBy\fP is the UTF-8 character (in native or \fI\\u\fP form) -to print before the number. The default is a single space. +to print before the number. +The default is a single space. . .TP \f(CBr\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) Here \fBy\fP is the UTF-8 character (in native or \fI\\u\fP form) -to print after the number. The default is \f(CB)\fP. +to print after the number. +The default is \f(CB)\fP. . .TP \f(CBa\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) Here \fBy\fP is '\f(CBleft\fP' (or one of its prefixes) if the number must be \fIleft\fP aligned, or '\f(CBright\fP' (or one of its prefixes) -if it must be \fIright\fP aligned. The default is \f(CBright\fP. +if it must be \fIright\fP aligned. +The default is \f(CBright\fP. . .TP \f(CBp\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) Here \fBy\fP is '\f(CBincluded\fP' (or one of its prefixes) or '\f(CBall\fP' (or one of its prefixes) for the initial \fIp\fPadding of -the non numbered words. '\f(CBincluded\fP' means that only \fIincluded\fP -word will be padded while '\f(CBall\fP' means pad \fIall\fP words. The -default is \f(CBall\fP. +the non numbered words. +'\f(CBincluded\fP' means that only \fIincluded\fP word will be padded +while '\f(CBall\fP' means pad \fIall\fP words. +The default is \f(CBall\fP. . .TP \f(CBw\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) @@ -887,6 +906,16 @@ \f(CBo\fP (\fB-F\fP option) Here \fBy\fP is the \fIo\fPffset of the first multibyte character of the number to extract from the word (defaults to \f(CB0\fP). + +If this offset if immediately followed by the character '\f(CB+\fP', +then the parser will look for the first number (if any) after the given +offset instead of using its absolute value to extract the number. + +Note that when the '\f(CB+\fP' is used, it is necessary that the length +of all the numbers to extract have the same size as the algorithm looks +for a digit to identify the beginning of the number to extract. +Hence, for example, \fB1\fP should appear as \fB01\fP in the input is +\f(CBn\fP is set to \f(CB2\fP. . .TP \f(CBn\fP (\fB-F\fP option) @@ -915,8 +944,8 @@ .TP \f(CBs\fP (\fB-F\fP, \fB-N\fP and \fB-U\fP options) Here \fBy\fP is the direct access number that will be set for the first -numbered word. Its value is \fB1\fP by default, a value of \fB0\fP -is possible. +numbered word. +Its value is \fB1\fP by default, a value of \fB0\fP is possible. .P Example: \f(CWr:\\> l:\\< a:l d:_ \fP @@ -1018,7 +1047,7 @@ \fI\\u\fP sequences can also be used here. .IP "\fB-T\fP [\fIseparator\fP]" -Enables the multi-selections or tag mode. +Enables the multiple selections or tag mode. In this mode, several selectable words can be selected without leaving the program. @@ -1040,9 +1069,9 @@ and earlier, you must also use the \fB-p\fP option. .IP "\fB-P\fP [\fIseparator\fP]" Works like \fB-T\fP but, unlike \fB-T\fP, the output depends on the order -in which the words were tagged. In other words, the first tagged word -comes first in the output, the second tagged word comes next, and so -on. +in which the words were tagged. +In other words, the first tagged word comes first in the output, the +second tagged word comes next, and so on. \fB-P\fP stands for "Pin". .IP \fB-p\fP This option modifies the default behavior of the \fB-T\fP and \fB-P\fP @@ -1067,8 +1096,9 @@ At the timeout, nothing is selected as if the \fBq\fP key has been pressed .TP 10 word: -At the timeout, the word given after the type is selected. Note that this -word doesn't need to be part of the words coming from the standard input. +At the timeout, the word given after the type is selected. +Note that this word doesn't need to be part of the words coming from +the standard input. .PP Each type can be be shortened as a prefix of its full name ("cur" for "current" of "q" for "quit" per example). @@ -1086,9 +1116,9 @@ messages is displayed above the selection window. .RE .IP "\fB-/\fP \fIsearch_method\fP" -Affects the '\fB/\fP' key to a search method. By default '\fB/\fP' -is affected to '\fIfuzzy\fP' but the argument can be any prefix -of '\fIprefix\fP', '\fIsubstring\fP' or '\fIfuzzy\fP'. +Affects the '\fB/\fP' key to a search method. +By default '\fB/\fP' is affected to '\fIfuzzy\fP' but the argument can +be any prefix of '\fIprefix\fP', '\fIsubstring\fP' or '\fIfuzzy\fP'. .SH NOTES If tabulators (\fI\\t\fP) are embedded in the input, there is no way to replace them with the original number of spaces. diff -Nru smenu-0.9.14/smenu.c smenu-0.9.15/smenu.c --- smenu-0.9.14/smenu.c 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/smenu.c 2019-03-30 14:08:14.000000000 +0000 @@ -14,15 +14,6 @@ /* General Public License for more details. */ /* ################################################################### */ -#define CHARSCHUNK 8 -#define WORDSCHUNK 8 -#define COLSCHUNK 16 - -#define TPARM1(p, ...) tparm(p, 0, 0, 0, 0, 0, 0, 0, 0, 0) -#define TPARM2(p, q, ...) tparm(p, q, 0, 0, 0, 0, 0, 0, 0, 0) -#define TPARM3(p, q, r, ...) tparm(p, q, r, 0, 0, 0, 0, 0, 0, 0) - -#define _XOPEN_SOURCE 700 #include "config.h" #include #include @@ -47,770 +38,54 @@ #include #include -/* Used for timers management */ -/* """""""""""""""""""""""""" */ -#define SECOND 1000000 -#define FREQ 10 -#define TICK (SECOND / FREQ) - -/* Bit array management */ -/* """""""""""""""""""" */ -#define MASK (CHAR_BIT - 1) -#define SHIFT ((CHAR_BIT == 8) ? 3 : (CHAR_BIT == 16) ? 4 : 8) - -#define BIT_OFF(a, x) ((void)((a)[(x) >> SHIFT] &= ~(1 << ((x)&MASK)))) -#define BIT_ON(a, x) ((void)((a)[(x) >> SHIFT] |= (1 << ((x)&MASK)))) -#define BIT_FLIP(a, x) ((void)((a)[(x) >> SHIFT] ^= (1 << ((x)&MASK)))) -#define BIT_ISSET(a, x) ((a)[(x) >> SHIFT] & (1 << ((x)&MASK))) - -/* ******** */ -/* Typedefs */ -/* ******** */ - -typedef struct charsetinfo_s charsetinfo_t; -typedef struct langinfo_s langinfo_t; -typedef struct ll_node_s ll_node_t; -typedef struct ll_s ll_t; -typedef struct term_s term_t; -typedef struct tst_node_s tst_node_t; -typedef struct toggle_s toggle_t; -typedef struct win_s win_t; -typedef struct word_s word_t; -typedef struct attr_s attr_t; -typedef struct limits_s limits_t; -typedef struct timers_s timers_t; -typedef struct misc_s misc_t; -typedef struct sed_s sed_t; -typedef struct interval_s interval_t; -typedef struct timeout_s timeout_t; -typedef struct output_s output_t; -typedef struct daccess_s daccess_t; -typedef struct search_data_s search_data_t; -typedef struct sub_tst_s sub_tst_t; - -typedef enum filter_types filters_t; -typedef enum daccess_modes da_mode_t; -typedef enum timeout_modes to_mode_t; -typedef enum attribute_settings attr_set_t; -typedef enum search_modes search_mode_t; -typedef enum bitmap_affinities bitmap_affinity_t; - -/* ********** */ -/* Prototypes */ -/* ********** */ - -static void -help(win_t * win, term_t * term, long last_line, toggle_t * toggle); - -static void -short_usage(void); - -static void -usage(void); - -static void * -xmalloc(size_t size); - -static void * -xcalloc(size_t num, size_t size); - -static void * -xrealloc(void * ptr, size_t size); - -static char * -xstrdup(const char * p); - -static char * -xstrndup(const char * str, size_t len); - -static char * -concat(const char * s1, ...); - -static interval_t * -interval_new(void); - -static int -interval_comp(void * a, void * b); - -static void -interval_swap(void * a, void * b); - -static int -tag_comp(void * a, void * b); - -static void -tag_swap(void * a, void * b); - -static int -ll_append(ll_t * const list, void * const data); - -#if 0 /* here for coherency but not used. */ -static int ll_prepend(ll_t * const list, void *const data); - -static void -ll_insert_before(ll_t * const list, ll_node_t * node, void *const data); - -static void -ll_insert_after(ll_t * const list, ll_node_t * node, void *const data); -#endif - -static ll_node_t * -ll_partition(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), - void (*swap)(void *, void *)); - -static void -ll_quicksort(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), - void (*swap)(void * a, void *)); - -static void -ll_sort(ll_t * list, int (*comp)(void *, void *), - void (*swap)(void * a, void *)); - -static int -ll_delete(ll_t * const list, ll_node_t * node); - -static ll_node_t * -ll_find(ll_t * const, void * const, int (*)(const void *, const void *)); - -static void -ll_init(ll_t * list); - -static ll_node_t * -ll_new_node(void); - -static ll_t * -ll_new(void); - -static void -ltrim(char * str, const char * trim); - -static void -rtrim(char * str, const char * trim, size_t min_len); - -static int -isempty(const char * str); - -static int -my_strcasecmp(const char * str1, const char * str2); - -static char * -my_strcpy(char * dst, char * src); - -static sub_tst_t * -sub_tst_new(void); - -static int -isprint7(int i); - -static int -isprint8(int i); - -void -beep(toggle_t * toggle); - -static int -get_cursor_position(int * const r, int * const c); - -static void -get_terminal_size(int * const r, int * const c); - -static int -mb_get_length(unsigned char c); - -static size_t -mb_offset(char *, size_t); - -static char * -mb_strprefix(char * d, char * s, long n, long * pos); - -static size_t -mb_strlen(char * str); - -static wchar_t * -mb_strtowcs(char * s); - -static void -mb_sanitize(char * s); - -static void -mb_interpret(char * s, langinfo_t * langinfo); - -static int -mb_validate(const char * str, size_t length); - -char * -mb_prev(const char * str, const char * p); - -char * -mb_next(char * p); - -static int -#ifdef __sun -outch(char c); -#else -outch(int c); -#endif - -static void -restore_term(int const fd); - -static void -setup_term(int const fd); - -static void -strip_ansi_color(char * s, toggle_t * toggle); - -static int -strprefix(char * str1, char * str2); - -static int -tst_cb(void * elem); - -static int -tst_cb_cli(void * elem); - -#if 0 /* here for coherency but not used. */ -static void tst_cleanup(tst_node_t * p); -#endif - -static tst_node_t * -tst_insert(tst_node_t * p, wchar_t * w, void * data); - -static void * -tst_prefix_search(tst_node_t * root, wchar_t * w, int (*callback)(void *)); - -static void * -tst_search(tst_node_t * root, wchar_t * w); - -static int -tst_traverse(tst_node_t * p, int (*callback)(void *), int first_call); - -static int -tst_substring_traverse(tst_node_t * p, int (*callback)(void *), int first_call, - wchar_t w); -static int -tst_fuzzy_traverse(tst_node_t * p, int (*callback)(void *), int first_call, - wchar_t w); - -void -mb_strtolower(char * dst, char * src); - -void -insert_sorted_index(long ** array, long * size, long * filled, long value); - -void -insert_sorted_ptr(tst_node_t *** array, unsigned long long * size, - unsigned long long * filled, tst_node_t * ptr); - -static int -ini_load(const char * filename, win_t * win, term_t * term, limits_t * limits, - timers_t * timers, misc_t * misc, - int (*report)(win_t * win, term_t * term, limits_t * limits, - timers_t * timers, misc_t * misc, const char * section, - const char * name, char * value)); - -static int -ini_cb(win_t * win, term_t * term, limits_t * limits, timers_t * timers, - misc_t * misc, const char * section, const char * name, char * value); - -static char * -make_ini_path(char * name, char * base); - -static void -set_foreground_color(term_t * term, short color); - -static void -set_background_color(term_t * term, short color); - -static void -set_win_start_end(win_t * win, long current, long last); - -static long -build_metadata(term_t * term, long count, win_t * win); - -static long -disp_lines(win_t * win, toggle_t * toggle, long current, long count, - search_mode_t search_mode, search_data_t * search_data, - term_t * term, long last_line, char * tmp_word, - langinfo_t * langinfo); - -static void -get_message_lines(char * message, ll_t * message_lines_list, - long * message_max_width, long * message_max_len); - -static void -disp_message(ll_t * message_lines_list, long width, long max_len, term_t * term, - win_t * win); - -static void -update_bitmaps(search_mode_t search_mode, search_data_t * search_data, - bitmap_affinity_t ending_pattern); - -long -find_next_matching_word(long * array, long nb, long value, long * index); - -long -find_prev_matching_word(long * array, long nb, long value, long * index); - -void -clean_matches(search_data_t * search_data, long size); - -void -disp_cursor_word(long pos, win_t * win, term_t * term, int err); - -void -disp_matching_word(long pos, win_t * win, term_t * term, int is_matching, - int err); - -static void -disp_word(long pos, search_mode_t search_mode, search_data_t * search_data, - term_t * term, win_t * win, char * tmp_word); - -static int -egetopt(int nargc, char ** nargv, char * ostr); - -static size_t -expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggle); - -static int -get_bytes(FILE * input, char * mb_buffer, langinfo_t * langinfo); +#include "xmalloc.h" +#include "list.h" +#include "index.h" +#include "utf8.h" +#include "fgetc.h" +#include "utils.h" +#include "getopt.h" +#include "usage.h" +#include "smenu.h" -static int -get_scancode(unsigned char * s, size_t max); - -static char * -get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list, - char * mb_buffer, unsigned char * is_last, toggle_t * toggle, - langinfo_t * langinfo, win_t * win, limits_t * limits); - -static void -left_margin_putp(char * s, term_t * term, win_t * win); - -static void -right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term, - win_t * win, long line, long offset); - -static void -sig_handler(int s); - -static void -set_new_first_column(win_t * win, term_t * term); - -static void -merge_intervals(ll_t * list); - -static int -parse_sed_like_string(sed_t * sed); - -static void -parse_selectors(char * str, filters_t * filter, char * unparsed, - ll_t ** inc_interval_list, ll_t ** inc_regex_list, - ll_t ** exc_interval_list, ll_t ** exc_regex_list, - langinfo_t * langinfo); - -static int -replace(char * orig, sed_t * sed); - -static int -decode_attr_toggles(char * s, attr_t * attr); - -static int -parse_attr(char * str, attr_t * attr, short max_color); - -static void -apply_attr(term_t * term, attr_t attr); - -static int (*my_isprint)(int); - -static int -delims_cmp(const void * a, const void * b); - -/* ***************** */ -/* Emums and structs */ -/* ***************** */ - -/* Various filter pseudo constants */ -/* """"""""""""""""""""""""""""""" */ -enum filter_types -{ - UNKNOWN_FILTER, - INCLUDE_FILTER, - EXCLUDE_FILTER -}; - -/* Used by the -N -F and -D options */ -/* """""""""""""""""""""""""""""""" */ -enum daccess_modes -{ - DA_TYPE_NONE = 0, /* must be 0 (boolean value) */ - DA_TYPE_AUTO = 1, - DA_TYPE_POS = 2 -}; - -/* Used when managing the -R option */ -/* """""""""""""""""""""""""""""""" */ -enum row_regex_types -{ - ROW_REGEX_EXCLUDE = 0, /* must be 0 (boolean value) */ - ROW_REGEX_INCLUDE = 1 -}; - -/* Used when managing the -C option */ -/* """""""""""""""""""""""""""""""" */ -enum filter_infos -{ - EXCLUDE_MARK = 0, /* must be 0 because used in various tests * - * these words cannot be re-included */ - INCLUDE_MARK = 1, /* to forcibly include a word, these words can * - * be excluded later */ - SOFT_EXCLUDE_MARK = 2, /* word with this mark are excluded by default * - * but can be included later */ - SOFT_INCLUDE_MARK = 3 /* word with this mark are included by default * - * but can be excluded later */ -}; - -enum timeout_modes -{ - CURRENT, /* on timeout, outputs the selected word */ - QUIT, /* on timeout, quit without selecting anything */ - WORD /* on timeout , outputs the specified word */ -}; - -enum attribute_settings -{ - UNSET = 0, /* must be 0 for future testings */ - SET, - FORCED /* an attribute setting has been given in the command line */ -}; - -enum search_modes -{ - NONE, - PREFIX, - FUZZY, - SUBSTRING -}; - -enum bitmap_affinities -{ - NO_AFFINITY, - END_AFFINITY, - START_AFFINITY -}; - -/* Locale informations */ -/* """"""""""""""""""" */ -struct langinfo_s -{ - int utf8; /* charset is UTF-8 */ - int bits; /* number of bits in the charset */ -}; - -struct charsetinfo_s -{ - char * name; /* canonical name of the allowed charset */ - int bits; /* number of bits in this charset */ -}; - -/* Various toggles which can be set with command line options */ -/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct toggle_s -{ - int del_line; /* 1 if the clean option is set (-d) else 0 */ - int enter_val_in_search; /* 1 if ENTER validates in search mode else 0 */ - int no_scrollbar; /* 1 to disable the scrollbar display else 0 */ - int blank_nonprintable; /* 1 to try to display non-blanks in * - * symbolic form else 0 */ - int keep_spaces; /* 1 to keep the trailing spaces in columns * - * and tabulate mode. */ - int taggable; /* 1 if tagging is enabled */ - int pinable; /* 1 if pinning is selected */ - int autotag; /* 1 if tagging is selected and pinning is * - * not and we do no want an automatic tagging * - * when the users presses */ - int visual_bell; /* 1 to flash the window, 0 to make a sound */ -}; - -/* Structure to store the default or imposed smenu limits */ -/* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct limits_s -{ - long word_length; /* maximum number of bytes in a word */ - long words; /* maximum number of words */ - long cols; /* maximum number of columns */ -}; - -/* Structure to store the default or imposed timers */ -/* """""""""""""""""""""""""""""""""""""""""""""""" */ -struct timers_s -{ - int search; - int help; - int winch; - int direct_access; -}; - -/* Structure to store miscellaneous informations */ -/* """"""""""""""""""""""""""""""""""""""""""""" */ -struct misc_s -{ - search_mode_t default_search_method; -}; - -/* Terminal setting variables */ -/* """""""""""""""""""""""""" */ -struct termios new_in_attrs; -struct termios old_in_attrs; - -/* Interval timers used */ -/* """""""""""""""""""" */ -struct itimerval periodic_itv; /* refresh rate for the timeout counter */ - -int help_timer = -1; -int winch_timer = -1; -int daccess_timer = -1; - -/* Structure containing the attributes components */ -/* """""""""""""""""""""""""""""""""""""""""""""" */ -struct attr_s -{ - attr_set_t is_set; - short fg; - short bg; - signed char bold; - signed char dim; - signed char reverse; - signed char standout; - signed char underline; - signed char italic; -}; +/* **************** */ +/* Extern variables */ +/* **************** */ -/* Structure containing some terminal characteristics */ -/* """""""""""""""""""""""""""""""""""""""""""""""""" */ -struct term_s -{ - int ncolumns; /* number of columns */ - int nlines; /* number of lines */ - int curs_column; /* current cursor column */ - int curs_line; /* current cursor line */ - short colors; /* number of available colors */ - short color_method; /* color method (0=classic (0-7), 1=ANSI) */ - - char has_cursor_up; /* has cuu1 terminfo capability */ - char has_cursor_down; /* has cud1 terminfo capability */ - char has_cursor_left; /* has cub1 terminfo capability */ - char has_cursor_right; /* has cuf1 terminfo capability */ - char has_parm_right_cursor; /* has cuf terminfo capability */ - char has_cursor_address; /* has cup terminfo capability */ - char has_save_cursor; /* has sc terminfo capability */ - char has_restore_cursor; /* has rc terminfo capability */ - char has_setf; /* has set_foreground terminfo capability */ - char has_setb; /* has set_background terminfo capability */ - char has_setaf; /* idem for set_a_foreground (ANSI) */ - char has_setab; /* idem for set_a_background (ANSI) */ - char has_hpa; /* has column_address terminfo capability */ - char has_bold; /* has bold mode */ - char has_dim; /* has dim mode */ - char has_reverse; /* has reverse mode */ - char has_underline; /* has underline mode */ - char has_standout; /* has standout mode */ - char has_italic; /* has italic mode */ -}; - -/* Structure describing a word */ -/* """"""""""""""""""""""""""" */ -struct word_s -{ - long start, end; /* start/end absolute horiz. word positions * - * on the screen */ - size_t mb; /* number of multibytes to display */ - long tag_order; /* each time a word is tagged, this value * - * is increased */ - size_t special_level; /* can vary from 0 to 5; 0 meaning normal */ - char * str; /* display string associated with this word */ - size_t len; /* number of bytes of str (for trimming) */ - char * orig; /* NULL or original string if is had been * - * shortened for being displayed or altered * - * by is expansion. */ - char * bitmap; /* used to store the the position of the * - * currently searched chars in a word. The * - * objective is to speed their display */ - unsigned char is_matching; - unsigned char is_tagged; /* 1 if the word is tagged, 0 if not */ - unsigned char is_last; /* 1 if the word is the last of a line */ - unsigned char is_selectable; /* word is selectable */ - unsigned char is_numbered; /* word has a direct access index */ -}; - -/* Structure describing the window in which the user */ -/* will be able to select a word */ -/* """"""""""""""""""""""""""""""""""""""""""""""""" */ -struct win_s -{ - long start, end; /* index of the first and last word */ - long first_column; /* number of the first character displayed */ - long cur_line; /* relative number of the cursor line (1+) */ - long asked_max_lines; /* requested number of lines in the window */ - long max_lines; /* effective number of lines in the window */ - long max_cols; /* max number of words in a single line */ - long real_max_width; /* max line length. In column, tab or line * - * mode it can be greater than the * - * terminal width */ - long message_lines; /* number of lines taken by the messages * - * (updated by disp_message */ - long max_width; /* max usable line width or the terminal */ - long offset; /* window offset user when centered */ - char * sel_sep; /* output separator when tags are enabled */ - char ** gutter_a; /* array of multibyte gutter characters */ - long gutter_nb; /* number of multibyte gutter characters */ - - unsigned char tab_mode; /* -t */ - unsigned char col_mode; /* -c */ - unsigned char line_mode; /* -l */ - unsigned char col_sep; /* -g */ - unsigned char wide; /* -w */ - unsigned char center; /* -M */ - - attr_t cursor_attr; /* current cursor attributes */ - attr_t cursor_on_tag_attr; /* current cursor on tag attributes */ - attr_t bar_attr; /* scrollbar attributes */ - attr_t shift_attr; /* shift indicator attributes */ - attr_t search_field_attr; /* search mode field attributes */ - attr_t search_text_attr; /* search mode text attributes */ - attr_t search_err_field_attr; /* bad search mode field attributes */ - attr_t search_err_text_attr; /* bad search mode text attributes */ - attr_t match_field_attr; /* matching word field attributes */ - attr_t match_text_attr; /* matching word text attributes */ - attr_t match_err_field_attr; /* bad matching word field attributes */ - attr_t match_err_text_attr; /* bad matching word text attributes */ - attr_t include_attr; /* selectable words attributes */ - attr_t exclude_attr; /* non-selectable words attributes */ - attr_t tag_attr; /* non-selectable words attributes */ - attr_t daccess_attr; /* direct access tag attributes */ - attr_t special_attr[5]; /* special (-1,...) words attributes */ -}; - -/* Sed like node structure */ -/* """"""""""""""""""""""" */ -struct sed_s -{ - char * pattern; /* pattern to be matched */ - char * substitution; /* substitution string */ - unsigned char visual; /* Visual flag: alterations are only * - * visual */ - unsigned char global; /* Global flag: alterations can * - * occur more than once */ - unsigned char stop; /* Stop flag: only one alteration * - * per word is allowed */ - regex_t re; -}; - -/* Interval structure for use in lists of columns and rows */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct interval_s -{ - long low; - long high; -}; - -/* Structure used by the replace function to delimit matches */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct range_s -{ - size_t start; - size_t end; -}; - -/* Structure used to keep track of the different timeout values */ -/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct timeout_s -{ - to_mode_t mode; /* timeout mode: current/quit/word */ - unsigned initial_value; /* 0: no timeout else value in sec */ - unsigned remain; /* remaining seconds */ - unsigned reached; /* 1: timeout has expired, else 0 */ -}; - -/* Structure used during the construction of the pinned words list */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct output_s -{ - long order; - char * output_str; -}; - -/* Structure describing the formating of the automatic direct access entries */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct daccess_s -{ - da_mode_t mode; /* DA_TYPE_NONE (0), DA_TYPE_AUTO, DA_TYPE_POS */ - char * left; /* character to put before the direct access selector */ - char * right; /* character to put after the direct access selector */ - char alignment; /* l: left; r: right */ - char padding; /* a: all; i: only included words are padded */ - char head; /* What to do with chars before the embedded number */ - int length; /* selector size (5 max) */ - int flength; /* 0 or length + 3 (full prefix lengh */ - size_t offset; /* offset to the start of the selector */ - int size; /* size in bytes of the selector to extract */ - size_t ignore; /* number of multibytes to ignore after the number */ - char follow; /* y: the numbering follows the last nuber set */ - char * num_sep; /* character to separate de number and the selection */ - int def_number; /* 1: the numbering is on by default 0: it is not */ -}; - -struct search_data_s -{ - char * buf; /* Search buffer */ - long len; /* Current position in the search buffer */ - long mb_len; /* Current position in the search buffer in * - * multibyte units */ - long * mb_off_a; /* Array of mb offsets in buf */ - long * mb_len_a; /* Array of mb lengths in buf */ - - int fuzzy_err; /* fuzzy match error indicator */ - long fuzzy_err_pos; /* last good position in search buffer */ - - int only_ending; /* only searches giving a result with the * - * pattern at the end of the word will be * - * selected */ - int only_starting; /* Same with the pattern at the beginning */ -}; - -/* Structure to contain data and metadata attached to a fuzzy/substring. */ -/* search step. */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -struct sub_tst_s -{ - tst_node_t ** array; - unsigned long long size; - unsigned long long count; -}; +extern ll_t * tst_search_list; +extern char * eoptarg; +extern char * eopterr; +extern int eoptind; +extern int eoptopt; +extern int eoptbad; +extern int eopterrfd; /* **************** */ /* Global variables */ /* **************** */ -word_t * word_a; /* Array containing words data (size: count) */ -long dummy_rc; /* temporary variable to silence the compiler */ +word_t * word_a; /* array containing words data (size: count) */ long count = 0; /* number of words read from stdin */ long current; /* index the current selection under the cursor) */ long new_current; /* final current position, (used in search function) */ long prev_current; /* previous position stored when using direct access */ -long * line_nb_of_word_a; /* array containing the line number * - * (from 0) of each word read */ -long * first_word_in_line_a; /* array containing the index of * - * the first word of each lines */ +long * line_nb_of_word_a; /* array containing the line number (from 0) * + * of each word read */ +long * first_word_in_line_a; /* array containing the index of the first * + * word of each lines */ + search_mode_t search_mode = NONE; search_mode_t old_search_mode = NONE; -int help_mode = 0; /* 1 if help is display else 0 */ +int help_mode = 0; /* 1 if help is displayed else 0 */ char * word_buffer; +int (*my_isprint)(int); + /* UTF-8 useful symbols */ -/* """"""""""""""""""""" */ +/* """""""""""""""""""" */ char * left_arrow = "\xe2\x86\x90"; char * up_arrow = "\xe2\x86\x91"; char * right_arrow = "\xe2\x86\x92"; @@ -825,16 +100,14 @@ char * sbar_arr_up = "\xe2\x96\xb2"; /* black_up_pointing_triangle */ char * sbar_arr_down = "\xe2\x96\xbc"; /* black_down_pointing_triangle */ -daccess_t daccess; - /* Variables used to manage the direct access entries */ /* """""""""""""""""""""""""""""""""""""""""""""""""" */ -char * daccess_stack; -int daccess_stack_head; +daccess_t daccess; +char * daccess_stack; +int daccess_stack_head; /* Variables used for fuzzy and substring searching */ /* """""""""""""""""""""""""""""""""""""""""""""""" */ -ll_t * tst_search_list = NULL; long * matching_words_a; long matching_words_a_size; long matches_count; @@ -851,283 +124,35 @@ volatile sig_atomic_t got_help_alrm = 0; volatile sig_atomic_t got_daccess_alrm = 0; volatile sig_atomic_t got_timeout_tick = 0; +volatile sig_atomic_t got_sigsegv = 0; +volatile sig_atomic_t got_sigterm = 0; +volatile sig_atomic_t got_sighup = 0; /* Variables used when a timeout is set (option -x) */ /* """""""""""""""""""""""""""""""""""""""""""""""" */ timeout_t timeout; -char * timeout_word; /* printed word when the timeout type is WORD. */ -char * timeout_seconds; /* string containing the number of remaining * - * seconds. */ -int quiet_timeout = 0; /* 1 when we want no message to be displayed. */ - -/* ************************************************************************ */ -/* Custom fgetc/ungetc implementation able to unget more than one character */ -/* ************************************************************************ */ - -enum -{ - GETC_BUFF_SIZE = 16 -}; - -static char getc_buffer[GETC_BUFF_SIZE] = { '\0' }; - -static long next_buffer_pos = 0; /* next free position in the getc buffer */ - -/* ====================================== */ -/* Get a (possibly pushed-back) character */ -/* ====================================== */ -static int -my_fgetc(FILE * input) -{ - return (next_buffer_pos > 0) ? getc_buffer[--next_buffer_pos] : fgetc(input); -} - -/* ============================ */ -/* Push character back on input */ -/* ============================ */ -static void -my_ungetc(int c) -{ - if (next_buffer_pos >= GETC_BUFF_SIZE) - fprintf(stderr, "Error: cannot push back more than %d characters\n", - GETC_BUFF_SIZE); - else - getc_buffer[next_buffer_pos++] = c; -} - -/* *************************************** */ -/* Ternary Search Tree specific structures */ -/* *************************************** */ - -/* Ternary node structure */ -/* """""""""""""""""""""" */ -struct tst_node_s -{ - wchar_t splitchar; - - tst_node_t *lokid, *eqkid, *hikid; - void * data; -}; - -/* ******************************* */ -/* Linked list specific structures */ -/* ******************************* */ - -/* Linked list node structure */ -/* """""""""""""""""""""""""" */ -struct ll_node_s -{ - void * data; - struct ll_node_s * next; - struct ll_node_s * prev; -}; - -/* Linked List structure */ -/* """"""""""""""""""""" */ -struct ll_s -{ - ll_node_t * head; - ll_node_t * tail; - long len; -}; +char * timeout_word; /* printed word when the timeout type is WORD. */ +char * timeout_seconds; /* string containing the number of remaining * + * seconds. */ +int quiet_timeout = 0; /* 1 when we want no message to be displayed. */ /* ************** */ /* Help functions */ /* ************** */ -/* =================== */ -/* Short usage display */ -/* =================== */ -static void -short_usage(void) -{ - fprintf(stderr, "Usage: smenu [-h|-?] [-f config_file] [-n lines] "); - fprintf(stderr, "[-t [cols]] [-k] [-v] \\\n"); - fprintf(stderr, " [-s pattern] [-m message] [-w] [-d] [-M] [-c] [-l] "); - fprintf(stderr, "[-r] [-b] \\\n"); - fprintf(stderr, " [-a prefix:attr [prefix:attr]...] "); - fprintf(stderr, "[-i regex] [-e regex] \\\n"); - fprintf(stderr, " [-C [i|e]] "); - fprintf(stderr, "[-R [i|e]] \\\n"); - fprintf(stderr, " [-S /regex/repl/[g][v][s][i]] "); - fprintf(stderr, "[-I /regex/repl/[g][v][s][i]] \\\n"); - fprintf(stderr, " [-E /regex/repl/[g][v][s][i]] "); - fprintf(stderr, "[-A regex] [-Z regex] \\\n"); - fprintf(stderr, " [-N [regex]] [-U [regex]] [-F] [-D arg...] "); - fprintf(stderr, " \\\n"); - fprintf(stderr, " [-1 regex [attr]] [-2 regex [attr]]... "); - fprintf(stderr, "[-5 regex [attr]] [-g [string]] \\\n"); - fprintf(stderr, " [-q] [-W bytes] [-L bytes] [-T [separator]] "); - fprintf(stderr, "[-P [separator]] [-p] \\\n"); - fprintf(stderr, " [-V] [-x|-X current|quit|word [] "); - fprintf(stderr, "] \\\n"); - fprintf(stderr, " [-/ prefix|substring|fuzzy] [--] [input_file]\n\n"); - fprintf(stderr, " ::= col1[-col2]...|...\n"); - fprintf(stderr, " ::= row1[-row2]...|...\n"); - fprintf(stderr, " ::= i|e|c|b|s|t|ct|sf|st|mf|mt|"); - fprintf(stderr, "sfe|ste|mfe|mte|da\n"); - fprintf(stderr, " ::= [l|r:]|[a:l|r]|[p:i|a]|"); - fprintf(stderr, "[w:]|\n"); - fprintf(stderr, " [f:y|n]|[o:]|[n:]|"); - fprintf(stderr, "[i:]|[d:]|\n"); - fprintf(stderr, " [s:]|[h:t|c|k]\n"); - fprintf(stderr, " Ex: l:'(' a:l\n"); - fprintf(stderr, " ::= [fg][/bg][,style] \n"); - fprintf(stderr, " Ex: 7/4,bu\n"); - fprintf(stderr, " ::= regex \n"); - fprintf(stderr, " Ex: /regex/ or :regex:\n\n"); - fprintf(stderr, " and can be freely mixed "); - fprintf(stderr, "when used\n"); - fprintf(stderr, " with -C and -R (ex: 2,/x/,4-5).\n"); -} - -/* ====================== */ -/* Usage display and exit */ -/* ====================== */ -static void -usage(void) -{ - short_usage(); - - fprintf(stderr, "\nThis is a filter that gets words from stdin "); - fprintf(stderr, "and outputs the selected\n"); - fprintf(stderr, "words (or nothing) on stdout in a nice selection "); - fprintf(stderr, "window\n\n"); - fprintf(stderr, "The selection window appears on /dev/tty "); - fprintf(stderr, "immediately after the current line\n"); - fprintf(stderr, "(no clear screen!).\n\n"); - fprintf(stderr, "The following options are available:\n\n"); - fprintf(stderr, "-h displays this help.\n"); - fprintf(stderr, "-f selects an alternative configuration file.\n"); - fprintf(stderr, "-n sets the number of lines in the selection window.\n"); - fprintf(stderr, - "-t tabulates the items. The number of columns can be limited " - "with\n"); - fprintf(stderr, " an optional number.\n"); - fprintf(stderr, - "-k does not trim spaces surrounding the output string if any.\n"); - fprintf(stderr, "-v makes the bell visual (fuzzy search with error).\n"); - fprintf(stderr, - "-s sets the initial cursor position (read the manual for " - "more details).\n"); - fprintf(stderr, "-m displays a one-line message above the window.\n"); - fprintf(stderr, - "-w uses all the terminal width for the columns if their numbers " - "is given.\n"); - fprintf(stderr, "-d deletes the selection window on exit.\n"); - fprintf(stderr, "-M centers the display if possible.\n"); - fprintf(stderr, - "-c is like -t without argument but respects end of lines.\n"); - fprintf(stderr, "-l is like -c without column alignments.\n"); - fprintf(stderr, - "-r enables ENTER to validate the selection even in " - "search mode.\n"); - fprintf(stderr, "-b displays the non printable characters as space.\n"); - fprintf(stderr, "-a sets the attributes for the various displayed "); - fprintf(stderr, "elements.\n"); - fprintf(stderr, - "-i sets the regex input filter to match the selectable words.\n"); - fprintf(stderr, - "-e sets the regex input filter to match the non-selectable " - "words.\n"); - fprintf(stderr, "-C sets columns restrictions for selections.\n"); - fprintf(stderr, "-R sets rows restrictions for selections.\n"); - fprintf(stderr, - "-S sets the post-processing action to apply to all words.\n"); - fprintf(stderr, - "-I sets the post-processing action to apply to selectable " - "words only.\n"); - fprintf(stderr, - "-E sets the post-processing action to apply to non-selectable " - "words only.\n"); - fprintf(stderr, - "-A forces a class of words to be the first of the line they " - "appear in.\n"); - fprintf(stderr, - "-Z forces a class of words to be the latest of the line they " - "appear in.\n"); - fprintf(stderr, - "-N/-U numbers/un-numbers and provides a direct access to words " - "matching\n"); - fprintf(stderr, " (or not) a specific regex.\n"); - fprintf(stderr, - "-F numbers and provides a direct access to words by extracting the " - "number\n"); - fprintf(stderr, " from the words.\n"); - fprintf(stderr, - "-D sets sub-options to modify the behaviour of -N, -U and -F.\n"); - fprintf(stderr, - "-1,-2,...,-5 gives specific colors to up to 5 classes of " - "selectable words.\n"); - fprintf(stderr, - "-g separates columns with a character in column or tabulate " - "mode.\n"); - fprintf(stderr, "-q prevents the display of the scroll bar.\n"); - fprintf(stderr, "-W sets the input words separators.\n"); - fprintf(stderr, "-L sets the input lines separators.\n"); - fprintf(stderr, "-T/-P enables the tagging (multi-selections) mode. "); - fprintf(stderr, "An optional parameter\n"); - fprintf(stderr, " sets the separator string between the selected words "); - fprintf(stderr, "on the output.\n"); - fprintf(stderr, " A single space is the default separator.\n"); - fprintf(stderr, "-p activates the auto-tagging when using -T or -P.\n"); - fprintf(stderr, "-V displays the current version and quits.\n"); - fprintf(stderr, - "-x|-X sets a timeout and specifies what to do when it expires.\n"); - fprintf(stderr, - "-/ changes the affectation of the / key (default fuzzy search).\n"); - fprintf(stderr, "\nNavigation keys are:\n"); - fprintf(stderr, " - Left/Down/Up/Right arrows or h/j/k/l, J/K.\n"); - fprintf(stderr, " - Home/End, SHIFT|CTRL+Home/End.\n"); - fprintf(stderr, " - Numbers if some words are numbered (-N/-U/-F).\n"); - fprintf(stderr, " - SPACE to search for the next match of a previously\n"); - fprintf(stderr, " entered search prefix if any, see below.\n\n"); - fprintf(stderr, "Other useful keys are:\n"); - fprintf(stderr, - " - Help key (temporary display of a short help line): " - "?\n"); - fprintf(stderr, - " - Exit key without output (do nothing) : " - "q\n"); - fprintf(stderr, - " - Tagging keys: Select/Deselect/Toggle : " - "INS/DEL/t\n"); - fprintf(stderr, - " - Selection key : " - "ENTER\n"); - fprintf(stderr, - " - Cancel key : " - "ESC\n"); - fprintf(stderr, - " - Search key : " - "/ or CTRL-F\n\n"); - fprintf(stderr, "The search key activates a timed search mode in which\n"); - fprintf(stderr, "you can enter the first letters of the searched word.\n"); - fprintf(stderr, "When entering this mode you have 7s to start typing\n"); - fprintf(stderr, "and each entered letter gives you 5 more seconds before\n"); - fprintf(stderr, "the timeout. After that the search mode is ended.\n\n"); - fprintf(stderr, "Notes:\n"); - fprintf(stderr, "- the timer can be cancelled by pressing ESC.\n"); - fprintf(stderr, "- a bad search letter can be removed with "); - fprintf(stderr, "CTRL-H or Backspace.\n\n"); - fprintf(stderr, "(C) Pierre Gentile (2015).\n"); - - exit(EXIT_FAILURE); -} - /* ==================== */ /* Help message display */ /* ==================== */ -static void +void help(win_t * win, term_t * term, long last_line, toggle_t * toggle) { int index; /* used to identify the objects long the help line */ - int line = 0; /* number of windows lines used by the help line */ - int len = 0; /* length of the help line */ - int offset = 0; /* offset from the first column of the terminal * - * to the start of the help line */ - int entries_nb; /* number of help entries to display */ - int help_len; /* total length of the help line */ + int line = 0; /* number of windows lines used by the help line */ + int len = 0; /* length of the help line */ + int offset = 0; /* offset from the first column of the terminal to * + * the start of the help line */ + int entries_nb; /* number of help entries to display */ + int help_len; /* total length of the help line */ struct entry_s { @@ -1222,119 +247,6 @@ tputs(TPARM1(restore_cursor), 1, outch); } -/* *************************** */ -/* Memory allocation functions */ -/* *************************** */ - -/* Created by Kevin Locke (from numerous canonical examples) */ -/* */ -/* I hereby place this file in the public domain. It may be freely */ -/* reproduced, distributed, used, modified, built upon, or otherwise */ -/* employed by anyone for any purpose without restriction. */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - -/* ================= */ -/* Customized malloc */ -/* ================= */ -static void * -xmalloc(size_t size) -{ - void * allocated; - size_t real_size; - - real_size = (size > 0) ? size : 1; - allocated = malloc(real_size); - if (allocated == NULL) - { - fprintf(stderr, - "Error: Insufficient memory (attempt to malloc %lu bytes)\n", - (unsigned long int)size); - - exit(EXIT_FAILURE); - } - - return allocated; -} - -/* ================= */ -/* Customized calloc */ -/* ================= */ -static void * -xcalloc(size_t n, size_t size) -{ - void * allocated; - - n = (n > 0) ? n : 1; - size = (size > 0) ? size : 1; - allocated = calloc(n, size); - if (allocated == NULL) - { - fprintf(stderr, - "Error: Insufficient memory (attempt to calloc %lu bytes)\n", - (unsigned long int)size); - - exit(EXIT_FAILURE); - } - - return allocated; -} - -/* ================== */ -/* Customized realloc */ -/* ================== */ -static void * -xrealloc(void * p, size_t size) -{ - void * allocated; - - allocated = realloc(p, size); - if (allocated == NULL && size > 0) - { - fprintf(stderr, - "Error: Insufficient memory (attempt to xrealloc %lu bytes)\n", - (unsigned long int)size); - - exit(EXIT_FAILURE); - } - - return allocated; -} - -/* =================================== */ -/* strdup implementation using xmalloc */ -/* =================================== */ -static char * -xstrdup(const char * p) -{ - char * allocated; - - allocated = xmalloc(strlen(p) + 1); - strcpy(allocated, p); - - return allocated; -} - -/* ================================================== */ -/* strndup implementation using xmalloc */ -/* This version guarantees that there is a final '\0' */ -/* ================================================== */ -static char * -xstrndup(const char * str, size_t len) -{ - char * p; - - p = memchr(str, '\0', len); - - if (p) - len = p - str; - - p = xmalloc(len + 1); - memcpy(p, str, len); - p[len] = '\0'; - - return p; -} - /* ********************************** */ /* Attributes string parsing function */ /* ********************************** */ @@ -1351,7 +263,7 @@ /* Returns 0 if some unexpected */ /* toggle is found else 0 */ /* ================================ */ -static int +int decode_attr_toggles(char * s, attr_t * attr) { int rc = 1; @@ -1404,7 +316,7 @@ /* Returns 1 on success else 0 */ /* attr will be filled by the function */ /* =========================================================*/ -static int +int parse_attr(char * str, attr_t * attr, short max_color) { int n; @@ -1472,7 +384,7 @@ /* ============================================= */ /* Set the terminal attributes according to attr */ /* ============================================= */ -static void +void apply_attr(term_t * term, attr_t attr) { if (attr.fg >= 0) @@ -1509,7 +421,7 @@ /* line of the ini file. */ /* Returns 0 if OK, 1 if not. */ /* ===================================================== */ -static int +int ini_cb(win_t * win, term_t * term, limits_t * limits, timers_t * timers, misc_t * misc, const char * section, const char * name, char * value) { @@ -1603,6 +515,7 @@ CHECK_ATTR(cursor) CHECK_ATTR(bar) CHECK_ATTR(shift) + CHECK_ATTR(message) CHECK_ATTR(search_field) CHECK_ATTR(search_text) CHECK_ATTR(match_field) @@ -1713,17 +626,17 @@ return error; } -/* ========================================================================= */ -/* Load an .ini format file */ -/* filename - path to a file */ -/* report - callback can return non-zero to stop, the callback error code is */ -/* returned from this function. */ -/* return - return 0 on success */ -/* */ -/* This function is public domain. No copyright is claimed. */ -/* Jon Mayo April 2011 */ -/* ========================================================================= */ -static int +/* ======================================================================== */ +/* Load an .ini format file */ +/* filename - path to a file */ +/* report - callback can return non-zero to stop, the callback error code */ +/* returned from this function. */ +/* return - return 0 on success */ +/* */ +/* This function is public domain. No copyright is claimed. */ +/* Jon Mayo April 2011 */ +/* ======================================================================== */ +int ini_load(const char * filename, win_t * win, term_t * term, limits_t * limits, timers_t * timers, misc_t * misc, int (*report)(win_t * win, term_t * term, limits_t * limits, @@ -1800,7 +713,7 @@ fclose(f); if (error) - fprintf(stderr, "Invalid entry: %s=%s in %s, exiting.\n", name, value, + fprintf(stderr, "Invalid entry found: %s=%s in %s.\n", name, value, filename); return error; @@ -1811,7 +724,7 @@ /* to be in the home directory of the user. */ /* NULL is returned if the built path is too large. */ /* ======================================================= */ -static char * +char * make_ini_path(char * name, char * base) { char * path; @@ -1820,6 +733,9 @@ long len; char * conf; + /* Set the prefix of the path from the environment */ + /* base can be "HOME" or "PWD". */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ home = getenv(base); if (home == NULL) @@ -1848,66 +764,14 @@ return path; } -/* ****************** */ -/* Interval functions */ -/* ****************** */ - -/* ===================== */ -/* Create a new interval */ -/* ===================== */ -static interval_t * -interval_new(void) -{ - interval_t * ret = xmalloc(sizeof(interval_t)); - - return ret; -} - -/* ====================================== */ -/* Compare 2 intervals as integer couples */ -/* same return values as for strcmp */ -/* ====================================== */ -static int -interval_comp(void * a, void * b) -{ - interval_t * ia = (interval_t *)a; - interval_t * ib = (interval_t *)b; - - if (ia->low < ib->low) - return -1; - if (ia->low > ib->low) - return 1; - if (ia->high < ib->high) - return -1; - if (ia->high > ib->high) - return 1; - - return 0; -} - -/* ================================ */ -/* Swap the values of two intervals */ -/* ================================ */ -static void -interval_swap(void * a, void * b) -{ - interval_t * ia = (interval_t *)a; - interval_t * ib = (interval_t *)b; - long tmp; - - tmp = ia->low; - ia->low = ib->low; - ib->low = tmp; - - tmp = ia->high; - ia->high = ib->high; - ib->high = tmp; -} +/* ******************************** */ +/* Functions used when sorting tags */ +/* ******************************** */ /* =========================================================== */ /* Compare the pin order of two pinned word in the output list */ /* =========================================================== */ -static int +int tag_comp(void * a, void * b) { output_t * oa = (output_t *)a; @@ -1922,7 +786,7 @@ /* ======================================================== */ /* Swap the values of two selected words in the output list */ /* ======================================================== */ -static void +void tag_swap(void * a, void * b) { output_t * oa = (output_t *)a; @@ -1939,310 +803,18 @@ ob->order = tmp_order; } -/* ********************* */ -/* Linked List functions */ -/* ********************* */ - -/* ======================== */ -/* Create a new linked list */ -/* ======================== */ -static ll_t * -ll_new(void) -{ - ll_t * ret = xmalloc(sizeof(ll_t)); - ll_init(ret); - - return ret; -} - -/* ======================== */ -/* Initialize a linked list */ -/* ======================== */ -static void -ll_init(ll_t * list) -{ - list->head = NULL; - list->tail = NULL; - list->len = 0; -} +/* ***************** */ +/* Utility functions */ +/* ***************** */ -/* ==================================================== */ -/* Allocate the space for a new node in the linked list */ -/* ==================================================== */ -static ll_node_t * -ll_new_node(void) +/* =================================================================== */ +/* Create a new element to be added to the tst_search_list used by the */ +/* search mechanism. */ +/* =================================================================== */ +sub_tst_t * +sub_tst_new(void) { - ll_node_t * ret = xmalloc(sizeof(ll_node_t)); - - if (ret == NULL) - errno = ENOMEM; - - return ret; -} - -/* ==================================================================== */ -/* Append a new node filled with its data at the end of the linked list */ -/* The user is responsible for the memory management of the data */ -/* ==================================================================== */ -static int -ll_append(ll_t * const list, void * const data) -{ - int ret = 1; - ll_node_t * node; - - if (list) - { - node = ll_new_node(); - if (node) - { - node->data = data; - node->next = NULL; - - node->prev = list->tail; - if (list->tail) - list->tail->next = node; - else - list->head = node; - - list->tail = node; - - ++list->len; - ret = 0; - } - } - - return ret; -} - -#if 0 /* here for coherency but not used. */ -/* =================================================================== */ -/* Put a new node filled with its data at the beginning of the linked */ -/* list. The user is responsible for the memory management of the data */ -/* =================================================================== */ -static int -ll_prepend(ll_t * const list, void *const data) -{ - int ret = 1; - ll_node_t *node; - - if (list) - { - node = ll_new_node(); - if (node) - { - node->data = data; - node->prev = NULL; - - node->next = list->head; - if (list->head) - list->head->prev = node; - else - list->tail = node; - - list->head = node; - - ++list->len; - ret = 0; - } - } - - return ret; -} - -/* ======================================================= */ -/* Insert a new node before the specified node in the list */ -/* TODO test it */ -/* ======================================================= */ -static void -ll_insert_before(ll_t * const list, ll_node_t * node, void *const data) -{ - ll_node_t *new_node; - - if (list) - { - if (node->prev == NULL) - ll_prepend(list, data); - else - { - new_node = ll_new_node(); - if (new_node) - { - new_node->data = data; - new_node->next = node; - new_node->prev = node->prev; - node->prev->next = new_node; - - ++list->len; - } - } - } -} - -/* ====================================================== */ -/* Insert a new node after the specified node in the list */ -/* TODO test it */ -/* ====================================================== */ -static void -ll_insert_after(ll_t * const list, ll_node_t * node, void *const data) -{ - ll_node_t *new_node; - - if (list) - { - if (node->next == NULL) - ll_append(list, data); - else - { - new_node = ll_new_node(); - if (new_node) - { - new_node->data = data; - new_node->prev = node; - new_node->next = node->next; - node->next->prev = new_node; - - ++list->len; - } - } - } -} -#endif - -/* ====================================================== */ -/* Partition code for the quicksort function */ -/* Based on code found here: */ -/* http://www.geeksforgeeks.org/quicksort-for-linked-list */ -/* ====================================================== */ -static ll_node_t * -ll_partition(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), - void (*swap)(void *, void *)) -{ - /* Considers last element as pivot, places the pivot element at its */ - /* correct position in sorted array, and places all smaller (smaller than */ - /* pivot) to left of pivot and all greater elements to right of pivot */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - - /* Set pivot as h element */ - /* """""""""""""""""""""" */ - void * x = h->data; - - ll_node_t * i = l->prev; - ll_node_t * j; - - for (j = l; j != h; j = j->next) - { - if (comp(j->data, x) < 1) - { - i = (i == NULL) ? l : i->next; - - swap(i->data, j->data); - } - } - - i = (i == NULL) ? l : i->next; - swap(i->data, h->data); - - return i; -} - -/* ======================================================= */ -/* A recursive implementation of quicksort for linked list */ -/* ======================================================= */ -static void -ll_quicksort(ll_node_t * l, ll_node_t * h, int (*comp)(void *, void *), - void (*swap)(void * a, void *)) -{ - if (h != NULL && l != h && l != h->next) - { - ll_node_t * p = ll_partition(l, h, comp, swap); - ll_quicksort(l, p->prev, comp, swap); - ll_quicksort(p->next, h, comp, swap); - } -} - -/* =========================== */ -/* A linked list sort function */ -/* =========================== */ -static void -ll_sort(ll_t * list, int (*comp)(void *, void *), - void (*swap)(void * a, void *)) -{ - /* Call the recursive ll_quicksort function */ - /* """""""""""""""""""""""""""""""""""""""" */ - ll_quicksort(list->head, list->tail, comp, swap); -} - -/* ================================ */ -/* Remove a node from a linked list */ -/* ================================ */ -static int -ll_delete(ll_t * const list, ll_node_t * node) -{ - if (list->head == list->tail) - { - if (list->head != NULL) - list->head = list->tail = NULL; - else - return 0; - } - else if (node->prev == NULL) - { - list->head = node->next; - list->head->prev = NULL; - } - else if (node->next == NULL) - { - list->tail = node->prev; - list->tail->next = NULL; - } - else - { - node->next->prev = node->prev; - node->prev->next = node->next; - } - - free(node); - - --list->len; - - return 1; -} - -/* =========================================================================*/ -/* Find a node in the list containing data. Return the node pointer or NULL */ -/* if not found. */ -/* A comparison function must be provided to compare a and b (strcmp like). */ -/* =========================================================================*/ -static ll_node_t * -ll_find(ll_t * const list, void * const data, - int (*cmpfunc)(const void * a, const void * b)) -{ - ll_node_t * node; - - if (NULL == (node = list->head)) - return NULL; - - do - { - if (0 == cmpfunc(node->data, data)) - return node; - } while (NULL != (node = node->next)); - - return NULL; -} - -/* ***************** */ -/* Utility functions */ -/* ***************** */ - -/* =================================================================== */ -/* Create a new element to be added to the tst_search_list used by the */ -/* search mechanism. */ -/* =================================================================== */ -sub_tst_t * -sub_tst_new(void) -{ - sub_tst_t * elem = xmalloc(sizeof(sub_tst_t)); + sub_tst_t * elem = xmalloc(sizeof(sub_tst_t)); elem->size = 64; elem->count = 0; @@ -2251,26 +823,9 @@ return elem; } -/* =============================== */ -/* 7 bits aware version of isprint */ -/* =============================== */ -static int -isprint7(int i) -{ - return (i >= 0x20 && i <= 0x7e); -} - -/* =============================== */ -/* 8 bits aware version of isprint */ -/* =============================== */ -static int -isprint8(int i) -{ - unsigned char c = i & (unsigned char)0xff; - - return (c >= 0x20 && c < 0x7f) || (c >= (unsigned char)0xa0); -} - +/* ======================================== */ +/* Emit a small (visual) beep warn the user */ +/* ======================================== */ void beep(toggle_t * toggle) { @@ -2299,151 +854,56 @@ } } -/* ========================================================= */ -/* Insertion of an integer in a already sorted integer array */ -/* without duplications. */ -/* ========================================================= */ -void -insert_sorted_index(long ** array, long * size, long * nb, long value) -{ - long pos = *nb; - long left = 0, right = *nb, middle; - - if (*nb > 0) - { - /* bisection search */ - /* """""""""""""""" */ - while (left < right) - { - middle = (left + right) / 2; - if ((*array)[middle] == value) - return; /* Value already in array */ - - if (value < (*array)[middle]) - right = middle; - else - left = middle + 1; - } - pos = left; - } - - if (*nb == *size) - { - *size += 64; - *array = xrealloc(*array, *size * sizeof(long)); - } - - if (*nb > pos) - memmove((*array) + pos + 1, (*array) + pos, sizeof(value) * (*nb - pos)); - - (*nb)++; - - (*array)[pos] = value; -} - -/* ========================================================= */ -/* Insertion of an pointer in a already sorted pointer array */ -/* without duplications. */ -/* ========================================================= */ +/* ======================================================================== */ +/* Update the bitmap associated with a word. This bitmap indicates the */ +/* positions of the UFT-8 glyphs of the search buffer in each word. */ +/* The disp_word function will use it to display these special characters. */ +/* ======================================================================== */ void -insert_sorted_ptr(tst_node_t *** array, unsigned long long * size, - unsigned long long * nb, tst_node_t * ptr) +update_bitmaps(search_mode_t mode, search_data_t * data, + bitmap_affinity_t affinity) { - unsigned long long pos = *nb; - unsigned long long left = 0, right = *nb, middle; - - if (*nb > 0) - { - /* bisection search */ - /* """""""""""""""" */ - while (left < right) - { - middle = (left + right) / 2; - if ((intptr_t)((*array)[middle]) == (intptr_t)ptr) - return; /* Value already in array */ - - if ((intptr_t)ptr < (intptr_t)((*array)[middle])) - right = middle; - else - left = middle + 1; - } - pos = left; - } - - if (*nb == *size) - { - *size += 64; - *array = xrealloc(*array, *size * sizeof(long)); - } + long i, j, n; /* work variables */ - if (*nb > pos) - memmove((*array) + pos + 1, (*array) + pos, sizeof(ptr) * (*nb - pos)); + long lmg; /* position of the last matching glyph of the search buffer * + * in a word */ - (*nb)++; + long sg; /* index going from lmg backward to 0 of the tested glyphs * + * of the search buffer (searched glyph) */ - (*array)[pos] = ptr; -} + long bm_len; /* number of chars taken by the bitmask */ -/* ============================================================== */ -/* Fill dst whi a lowercase ocopy of src whar the character is an */ -/* ascci one. dsk must be preallocated before the call. */ -/* ============================================================== */ -void -mb_strtolower(char * dst, char * src) -{ - unsigned char c; + char * start; /* pointer on the position of the matching position * + * of the last search buffer glyph in the word */ - while ((c = *src)) - { - if (c >= 0x80) - *dst = c; - else - *dst = tolower(c); + char * bm; /* the word's current bitmap */ - src++; - dst++; - } - *dst = '\0'; -} + char * str; /* copy of the current word put in lower case */ + char * str_orig; /* oiginal version of the word */ -/* ======================================================================== */ -/* Update the bitmap associated with a word. This bitmap indicates the */ -/* positions of the searched mb characters previously entered in the search */ -/* buffer. */ -/* The disp_word function will use it to display these special characters. */ -/* ======================================================================== */ -static void -update_bitmaps(search_mode_t mode, search_data_t * data, - bitmap_affinity_t affinity) -{ - long i, j, n; - long fc, bc; - long mb; - char * start; - char * p; - char * bm; - char * str; - char * str_orig; - char * first_mb; + char * first_glyph; - char * sb_orig = data->buf; + char * sb_orig = data->buf; /* sb: search buffer */ char * sb; - long * o = data->mb_off_a; - long * l = data->mb_len_a; - long last = data->mb_len - 1; + long * o = data->utf8_off_a; /* array of the offsets of the search * + * buffer glyphs */ + long * l = data->utf8_len_a; /* array of the lengths in bytes of the * + * search buffer glyphs */ + long last = data->utf8_len - 1; /* offset of the last glyph in the * + * search buffer */ - long badness; + long badness = 0; /* number of 0s between two 1s */ best_matches_count = 0; if (mode == FUZZY || mode == SUBSTRING) { - first_mb = xmalloc(5); + first_glyph = xmalloc(5); if (mode == FUZZY) { sb = xstrdup(sb_orig); - mb_strtolower(sb, sb_orig); + utf8_strtolower(sb, sb_orig); } else sb = sb_orig; @@ -2460,33 +920,46 @@ /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ str_orig[word_a[n].len] = '\0'; - mb = (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1; - bm = word_a[n].bitmap; + bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1; + bm = word_a[n].bitmap; + /* In fuzzy search mode str is converted in lowercases */ + /* for comparison reason. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ if (mode == FUZZY) { str = xstrdup(str_orig); - mb_strtolower(str, str_orig); + utf8_strtolower(str, str_orig); } else str = str_orig; start = str; - fc = 0; - while (start) + lmg = 0; + + /* starts points to the first UTF-8 glyph og the word */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + while ((size_t)(start - str) < word_a[n].len - daccess.flength) { /* Reset the bitmap */ /* """""""""""""""" */ - memset(bm, '\0', mb); + memset(bm, '\0', bm_len); + /* Compare the glyph pointed to by start to the last glyph of */ + /* the search buffer, the aim is to pinte to the first */ + /* occurrence of the last glyph of it. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (memcmp(start, sb + o[last], l[last]) == 0) { - /* As there is only on glyph in the search buffer, we can */ - /* stop here. */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ + char * p; /* Pointer to the beginning of an UTF-8 glyph in * + * the potential lowercase version of the word */ + if (last == 0) { - BIT_ON(bm, fc); + /* There is only one glyph in the search buffer, we can */ + /* stop here. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ + BIT_ON(bm, lmg); if (affinity != END_AFFINITY) break; } @@ -2496,66 +969,69 @@ /* the word. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ p = start; - j = last; + j = last; /* j counts the number of glyphs in the search buffer * + * not found in the word */ /* Proceed backwards from the position of last glyph of the search */ - /* to check if all the previous glyphs ca be fond before in the */ + /* to check if all the previous glyphs can be fond before in the */ /* word. If not try to find the next position of this last glyph */ /* in the word. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - bc = fc; - while (j > 0 && (p = mb_prev(str, p)) != NULL) + sg = lmg; + while (j > 0 && (p = utf8_prev(str, p)) != NULL) { if (memcmp(p, sb + o[j - 1], l[j - 1]) == 0) { - BIT_ON(bm, bc - 1); + BIT_ON(bm, sg - 1); j--; } else if (mode == SUBSTRING) break; - bc--; + sg--; } /* All the glyphs have been found */ /* """""""""""""""""""""""""""""" */ if (j == 0) { - BIT_ON(bm, fc); + BIT_ON(bm, lmg); if (affinity != END_AFFINITY) break; } } - fc++; - start = mb_next(start); + lmg++; + start = utf8_next(start); } + if (mode == FUZZY) { - size_t mb_index; + size_t utf8_index; free(str); /* We know that the first glyph is part of the pattern, so */ /* highlight it if it is not and un-highlight the next occurrence */ /* that must be here because this word has already been filtered */ - /* by select_staring_pattern() */ + /* by select_starting_pattern() */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (affinity == START_AFFINITY) { char *ptr1, *ptr2; long i = 1; - long mb_len; + long utf8_len; - first_mb = mb_strprefix(first_mb, word_a[n].str, 1, &mb_len); + first_glyph = utf8_strprefix(first_glyph, word_a[n].str, 1, + &utf8_len); if (!BIT_ISSET(word_a[n].bitmap, 0)) { BIT_ON(word_a[n].bitmap, 0); ptr1 = word_a[n].str; - while ((ptr2 = mb_next(ptr1)) != NULL) + while ((ptr2 = utf8_next(ptr1)) != NULL) { - if (memcmp(ptr2, first_mb, mb_len) == 0) + if (memcmp(ptr2, first_glyph, utf8_len) == 0) { if (BIT_ISSET(word_a[n].bitmap, i)) { @@ -2577,29 +1053,29 @@ /* badness of a match. Th goal is to put the cursor on the word */ /* with the smallest badness. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_index = 0; - j = 0; - badness = 0; + utf8_index = 0; + j = 0; + badness = 0; - while (mb_index < word_a[n].mb - 1 - && !BIT_ISSET(word_a[n].bitmap, mb_index)) - mb_index++; + while (utf8_index < word_a[n].mb + && !BIT_ISSET(word_a[n].bitmap, utf8_index)) + utf8_index++; - while (mb_index < word_a[n].mb - 1) + while (utf8_index < word_a[n].mb) { - if (!BIT_ISSET(word_a[n].bitmap, mb_index)) + if (!BIT_ISSET(word_a[n].bitmap, utf8_index)) badness++; else j++; - /* Stop here if all the possible bits has been checked as they */ - /* cannot be more numerous than the number of multibytes in */ - /* the search buffer. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (j == data->mb_len) + /* Stop here if all the possible bits has been checked as they */ + /* cannot be more numerous than the number of UTF-8 glyphs in */ + /* the search buffer. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (j == data->utf8_len) break; - mb_index++; + utf8_index++; } } free(str_orig); @@ -2629,17 +1105,17 @@ if (mode == FUZZY) free(sb); - free(first_mb); + free(first_glyph); } else if (mode == PREFIX) { for (i = 0; i < matches_count; i++) { - n = matching_words_a[i]; - bm = word_a[n].bitmap; - mb = (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1; + n = matching_words_a[i]; + bm = word_a[n].bitmap; + bm_len = (word_a[n].mb - daccess.flength) / CHAR_BIT + 1; - memset(bm, '\0', mb); + memset(bm, '\0', bm_len); for (j = 0; j <= last; j++) BIT_ON(bm, j); @@ -2794,8 +1270,9 @@ /* Clean the search buffer */ /* """"""""""""""""""""""" */ memset(search_data->buf, '\0', size - daccess.flength); + search_data->len = 0; - search_data->mb_len = 0; + search_data->utf8_len = 0; search_data->only_ending = 0; search_data->only_starting = 0; @@ -2808,7 +1285,7 @@ word_a[n].is_matching = 0; memset(word_a[n].bitmap, '\0', - (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1); + (word_a[n].mb - daccess.flength) / CHAR_BIT + 1); } matches_count = 0; @@ -2822,7 +1299,7 @@ /* outch is a function version of putchar that can be passed to tputs as */ /* a routine to call. */ /* ===================================================================== */ -static int +int #ifdef __sun outch(char c) #else @@ -2837,7 +1314,7 @@ /* Set the terminal in non echo/non canonical mode */ /* wait for at least one byte, no timeout. */ /* =============================================== */ -static void +void setup_term(int const fd) { /* Save the terminal parameters and configure it in row mode */ @@ -2858,7 +1335,7 @@ /* ===================================== */ /* Set the terminal in its previous mode */ /* ===================================== */ -static void +void restore_term(int const fd) { tcsetattr(fd, TCSANOW, &old_in_attrs); @@ -2869,7 +1346,7 @@ /* Assume that the TIOCGWINSZ, ioctl is available */ /* Defaults to 80x24 */ /* ============================================== */ -static void +void get_terminal_size(int * const r, int * const c) { struct winsize ws; @@ -2896,7 +1373,7 @@ /* Get cursor position the terminal */ /* Assume that the Escape sequence ESC [ 6 n is available */ /* ====================================================== */ -static int +int get_cursor_position(int * const r, int * const c) { char buf[32]; @@ -2932,42 +1409,10 @@ return 1; } -/* *********************************************** */ -/* Strings and multibyte strings utility functions */ -/* *********************************************** */ - -/* ======================= */ -/* Trim leading characters */ -/* ======================= */ -static void -ltrim(char * str, const char * trim_str) -{ - size_t len = strlen(str); - size_t begin = strspn(str, trim_str); - size_t i; - - if (begin > 0) - for (i = begin; i <= len; ++i) - str[i - begin] = str[i]; -} - -/* ================================================= */ -/* Trim trailing characters */ -/* The resulting string will have at least min bytes */ -/* even if trailing spaces remain. */ -/* ================================================= */ -static void -rtrim(char * str, const char * trim_str, size_t min) -{ - size_t len = strlen(str); - while (len > min && strchr(trim_str, str[len - 1])) - str[--len] = '\0'; -} - /* ===================================================== */ /* Returns 1 if a string is empty or only made of spaces */ /* ===================================================== */ -static int +int isempty(const char * s) { while (*s != '\0') @@ -2979,48 +1424,6 @@ return 1; } -/* ========================================= */ -/* Case insensitive strcmp */ -/* from http://c.snippets.org/code/stricmp.c */ -/* ========================================= */ -static int -my_strcasecmp(const char * str1, const char * str2) -{ -#ifdef HAVE_STRCASECMP - return strcasecmp(str1, str2); -#else - int retval = 0; - - while (1) - { - retval = tolower(*str1++) - tolower(*str2++); - - if (retval) - break; - - if (*str1 && *str2) - continue; - else - break; - } - return retval; -#endif -} - -/* =========================================== */ -/* memmove based strcpy (tolerates overlaping) */ -/* =========================================== */ -char * -my_strcpy(char * str1, char * str2) -{ - if (str1 == NULL || str2 == NULL) - return NULL; - - memmove(str1, str2, strlen(str2) + 1); - - return str1; -} - /* ======================================================================== */ /* Parse a regular expression based selector. */ /* The string to parse is bounded by a delimiter so we must parse something */ @@ -3033,7 +1436,7 @@ /* exc_regex_list (out) INCLUDE_FILTER or EXCLUDE_FILTER */ /* regular expression if the filter is EXCLUDE_FILTER */ /* ======================================================================== */ -static void +void parse_regex_selector_part(char * str, filters_t filter, ll_t ** inc_regex_list, ll_t ** exc_regex_list) { @@ -3086,7 +1489,7 @@ /* exc_regex_list (out) is a list of extended interval of elements to */ /* be excluded. */ /* ===================================================================== */ -static void +void parse_selectors(char * str, filters_t * filter, char * unparsed, ll_t ** inc_interval_list, ll_t ** inc_regex_list, ll_t ** exc_interval_list, ll_t ** exc_regex_list, @@ -3102,7 +1505,7 @@ /* Replace the UTF-8 ascii representation in the selector by */ /* their binary values. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_interpret(str, langinfo); + utf8_interpret(str, langinfo); /* Get the first character to see if this is */ /* an additive or restrictive operation. */ @@ -3306,60 +1709,11 @@ *unparsed = '\0'; } -/* ===================================================================== */ -/* Merge the intervals from an interval list in order to get the minimum */ -/* number of intervals to consider. */ -/* ===================================================================== */ -static void -merge_intervals(ll_t * list) -{ - ll_node_t * node1, *node2; - interval_t *data1, *data2; - - if (!list || list->len < 2) - return; - else - { - /* Step 1: sort the intervals list */ - /* """"""""""""""""""""""""""""""" */ - ll_sort(list, interval_comp, interval_swap); - - /* Step 2: merge the list by merging the consecutive intervals */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - node1 = list->head; - node2 = node1->next; - - while (node2) - { - data1 = (interval_t *)(node1->data); - data2 = (interval_t *)(node2->data); - - if (data1->high >= data2->low - 1) - { - /* Interval 1 overlaps interval 2 */ - /* '''''''''''''''''''''''''''''' */ - if (data2->high >= data1->high) - data1->high = data2->high; - ll_delete(list, node2); - free(data2); - node2 = node1->next; - } - else - { - /* No overlap */ - /* '''''''''' */ - node1 = node2; - node2 = node2->next; - } - } - } -} - -/* ======================================================== */ -/* Parse the sed like string passed as argument to -S/-I/-E */ -/* ======================================================== */ -static int -parse_sed_like_string(sed_t * sed) +/* ======================================================== */ +/* Parse the sed like string passed as argument to -S/-I/-E */ +/* ======================================================== */ +int +parse_sed_like_string(sed_t * sed) { char sep; char * first_sep_pos; @@ -3446,12 +1800,12 @@ /* ===================================================================== */ /* Utility function used by replace to expand the replacement string */ /* IN: */ -/* to_match: matching part of the original string to be replaced */ +/* orig: matching part of the original string to be replaced */ /* repl: string containing the replacement directives */ /* subs_a: array of ranges containing the start and end offset */ /* of the remembered parts of the strings referenced by */ /* the sequence \n where n is in [1,10] */ -/* match_start/end: offset in to_match for the current matched string */ +/* match_start/end: offset in orig for the current matched string */ /* subs_nb: number of elements containing significant values in */ /* the array described above */ /* match: current match number in the original string */ @@ -3459,10 +1813,9 @@ /* OUT: */ /* The modified string according to the content of repl */ /* ===================================================================== */ -static char * -build_repl_string(char * to_match, char * repl, long match_start, - long match_end, struct range_s * subs_a, long subs_nb, - long match) +char * +build_repl_string(char * orig, char * repl, long match_start, long match_end, + range_t * subs_a, long subs_nb, long match) { size_t rsize = 0; size_t allocated = 16; @@ -3511,7 +1864,7 @@ if (allocated <= rsize + delta) str = xrealloc(str, allocated += (delta + 16)); - memcpy(str + rsize, to_match + subs_a[index].start, delta); + memcpy(str + rsize, orig + subs_a[index].start, delta); rsize += delta; str[rsize] = '\0'; @@ -3537,7 +1890,7 @@ if (allocated <= rsize + delta) str = xrealloc(str, allocated += (delta + 16)); - memcpy(str + rsize, to_match + match_start, delta); + memcpy(str + rsize, orig + match_start, delta); rsize += delta; str[rsize] = '\0'; @@ -3567,7 +1920,7 @@ /* by the substitution string */ /* The regex used must have been previously compiled */ /* */ -/* to_match: original string */ +/* orig: original string */ /* sed: composite variable containing the regular expression, a */ /* substitution string and various other informations. */ /* output: destination buffer */ @@ -3578,8 +1931,8 @@ /* NOTE: */ /* uses the global variable word_buffer */ /* ====================================================================== */ -static int -replace(char * to_match, sed_t * sed) +int +replace(char * orig, sed_t * sed) { size_t match_nb = 0; /* number of matches in the original string */ int sub_nb = 0; /* number of remembered matches in the * @@ -3587,17 +1940,15 @@ size_t target_len = 0; /* length of the resulting string */ size_t subs_max = 0; - if (*to_match == '\0') + if (*orig == '\0') return 1; - struct range_s * matches_a = xmalloc(strlen(to_match) - * sizeof(struct range_s)); + range_t * matches_a = xmalloc(strlen(orig) * sizeof(range_t)); + range_t * subs_a = xmalloc(10 * sizeof(range_t)); - struct range_s * subs_a = xmalloc(10 * sizeof(struct range_s)); - - const char * p = to_match; /* points to the end of the previous match. */ - regmatch_t m[10]; /* array containing the start/end offsets * - * of the matches found. */ + const char * p = orig; /* points to the end of the previous match. */ + regmatch_t m[10]; /* array containing the start/end offsets * + * of the matches found. */ while (1) { @@ -3617,14 +1968,14 @@ if (match_nb > 0) { for (index = 0; index < matches_a[0].start; index++) - word_buffer[target_len++] = to_match[index]; + word_buffer[target_len++] = orig[index]; for (match = 0; match < match_nb; match++) { size_t len; size_t end; - exp_repl = build_repl_string(to_match, sed->substitution, + exp_repl = build_repl_string(orig, sed->substitution, matches_a[match].start, matches_a[match].end, subs_a, subs_max, match); @@ -3640,10 +1991,10 @@ if (match < match_nb - 1 && sed->global) end = matches_a[match + 1].start; else - end = strlen(to_match); + end = strlen(orig); while (index < end) - word_buffer[target_len++] = to_match[index++]; + word_buffer[target_len++] = orig[index++]; word_buffer[target_len] = '\0'; @@ -3653,7 +2004,7 @@ } else { - my_strcpy(word_buffer, to_match); + my_strcpy(word_buffer, orig); return 0; } @@ -3669,15 +2020,15 @@ if (m[i].rm_so == -1) break; - start = m[i].rm_so + (p - to_match); - finish = m[i].rm_eo + (p - to_match); + start = m[i].rm_so + (p - orig); + finish = m[i].rm_eo + (p - orig); if (i == 0) { matches_a[match_nb].start = start; matches_a[match_nb].end = finish; match_nb++; - if (match_nb > mb_strlen(to_match)) + if (match_nb > utf8_strlen(orig)) goto fail; } else @@ -3702,7 +2053,7 @@ /* Remove all ANSI color codes from s and puts the result in d. */ /* Memory space for d must have been allocated before. */ /* ============================================================ */ -static void +void strip_ansi_color(char * s, toggle_t * toggle) { char * p = s; @@ -3738,1550 +2089,843 @@ *p = '\0'; } -/* ======================================================================== */ -/* Unicode (UTF-8) ascii representation interpreter. */ -/* The string passed will be altered but will not move in memory */ -/* All sequence of \uxx, \uxxxx, \uxxxxxx and \uxxxxxxxx will be replace by */ -/* the corresponding UTF-8 character. */ -/* ======================================================================== */ -void -mb_interpret(char * s, langinfo_t * langinfo) +/* ================================================================ */ +/* Callback to add a word index in the sorted list of matched words */ +/* ================================================================ */ +int +set_matching_flag(void * elem) { - char * utf8_str; /* \uxx... */ - size_t utf8_to_eos_len; /* bytes in s starting from the first * - * occurrence of \u */ - size_t init_len; /* initial lengths of the string to interpret */ - size_t utf8_ascii_len; /* 2,4,6 or 8 bytes */ - size_t len_to_remove = 0; /* number of bytes to remove after the conversion */ - char tmp[9]; /* temporary string */ - - /* Guard against the case where s is NULL */ - /* """""""""""""""""""""""""""""""""""""" */ - if (s == NULL) - return; + ll_t * list = (ll_t *)elem; - init_len = strlen(s); + ll_node_t * node = list->head; - while ((utf8_str = strstr(s, "\\u")) != NULL) + while (node) { - utf8_to_eos_len = strlen(utf8_str); - if (utf8_to_eos_len - < 4) /* string too short to contain a valid UTF-8 char */ - { - *utf8_str = '.'; - *(utf8_str + 1) = '\0'; - } - else /* s is long enough */ - { - unsigned byte; - char * utf8_seq_offset = utf8_str + 2; - - /* Get the first 2 utf8 bytes */ - *tmp = *utf8_seq_offset; - *(tmp + 1) = *(utf8_seq_offset + 1); - *(tmp + 2) = '\0'; - - /* If they are invalid, replace the \u sequence by a dot */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!isxdigit(tmp[0]) || !isxdigit(tmp[1])) - { - *utf8_str = '.'; - if (4 >= utf8_to_eos_len) - *(utf8_str + 1) = '\0'; - else - memmove(utf8_str, utf8_str + 4, utf8_to_eos_len - 4); - return; - } - else - { - /* They are valid, deduce from them the length of the sequence */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - sscanf(tmp, "%2x", &byte); - utf8_ascii_len = mb_get_length(byte) * 2; - - /* Check again if the inputs string is long enough */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (utf8_to_eos_len - 2 < utf8_ascii_len) - { - *utf8_str = '.'; - *(utf8_str + 1) = '\0'; - } - else - { - /* replace the \u sequence by the bytes forming the UTF-8 char */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - size_t i; - *tmp = byte; - - /* Put the bytes in the tmp string */ - /* ''''''''''''''''''''''''''''''' */ - if (langinfo->utf8) - { - for (i = 1; i < utf8_ascii_len / 2; i++) - { - sscanf(utf8_seq_offset + 2 * i, "%2x", &byte); - *(tmp + i) = byte; - } - tmp[utf8_ascii_len / 2] = '\0'; - } + size_t pos; - /* Does they form a valid UTF-8 char? */ - /* '''''''''''''''''''''''''''''''''' */ - if (langinfo->utf8 && mb_validate(tmp, utf8_ascii_len / 2)) - { - /* Put them back in the original string and move */ - /* the remaining bytes after them */ - /* ''''''''''''''''''''''''''''''''''''''''''''' */ - memmove(utf8_str, tmp, utf8_ascii_len / 2); + pos = *(size_t *)(node->data); + if (word_a[pos].is_selectable) + word_a[pos].is_matching = 1; - if (utf8_to_eos_len < utf8_ascii_len) - *(utf8_str + utf8_ascii_len / 2 + 1) = '\0'; - else - memmove(utf8_str + utf8_ascii_len / 2, - utf8_seq_offset + utf8_ascii_len, - utf8_to_eos_len - utf8_ascii_len - 2 + 1); - } - else - { - /* The invalid sequence is replaced by a dot */ - /* ''''''''''''''''''''''''''''''''''''''''' */ - *utf8_str = '.'; - if (utf8_to_eos_len < utf8_ascii_len) - *(utf8_str + 1) = '\0'; - else - memmove(utf8_str + 1, utf8_seq_offset + utf8_ascii_len, - utf8_to_eos_len - utf8_ascii_len - 2 + 1); - utf8_ascii_len = 2; - } - } + insert_sorted_index(&matching_words_a, &matching_words_a_size, + &matches_count, pos); - /* Update the number of bytes to remove at the end */ - /* of the initial string */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - len_to_remove += 2 + utf8_ascii_len / 2; - } - } + node = node->next; } - - /* Make sure that the string is well terminated */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - *(s + init_len - len_to_remove) = '\0'; - - return; + return 1; } -/* ========================================================= */ -/* Decode the number of bytes taken by a character (UTF-8) */ -/* It is the length of the leading sequence of bits set to 1 */ -/* (Count Leading Ones) */ -/* ========================================================= */ -static int -mb_get_length(unsigned char c) +/* ======================================================================= */ +/* Callback function used by tst_traverse */ +/* Iterate the linked list attached to the string containing the index of */ +/* the words in the input flow. Each page number is then added in a sorted */ +/* array avoiding duplications keeping the array sorted. */ +/* Mark the identified words as a matching word. */ +/* ======================================================================= */ +int +tst_cb(void * elem) { - if (c >= 0xf0) - return 4; - else if (c >= 0xe0) - return 3; - else if (c >= 0xc2) - return 2; - else - return 1; -} + /* The data attached to the string in the tst is a linked list of position */ + /* of the string in the input flow, This list is naturally sorted */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + ll_t * list = (ll_t *)elem; -/* ========================================================== */ -/* Return the byte offset of the nth multibyte character in s */ -/* ========================================================== */ -static size_t -mb_offset(char * s, size_t n) -{ - size_t i = 0; + ll_node_t * node = list->head; - while (n > 0) + while (node) { - if (s[i++] & 0x80) - { - (void)(((s[++i] & 0xc0) != 0x80) || ((s[++i] & 0xc0) != 0x80) || ++i); - } - n--; - } - return i; -} + size_t pos; -/* ================================================== */ -/* Points to the previous multibyte glyph in a string */ -/* from the given position */ -/* ================================================== */ -char * -mb_prev(const char * str, const char * p) -{ - while ((*p & 0xc0) == 0x80) - p--; + pos = *(long *)(node->data); - for (--p; p >= str; --p) - { - if ((*p & 0xc0) != 0x80) - return (char *)p; - } - return NULL; -} + word_a[pos].is_matching = 1; + insert_sorted_index(&matching_words_a, &matching_words_a_size, + &matches_count, pos); -/* ============================================== */ -/* Points to the next multibyte glyph in a string */ -/* from the current position */ -/* ============================================== */ -char * -mb_next(char * p) -{ - if (*p) - { - for (++p; (*p & 0xc0) == 0x80; ++p) - ; + node = node->next; } - return (*p == '\0' ? NULL : p); + return 1; } -/* ============================================================ */ -/* Replace any multibyte present in s by a dot in-place */ -/* s will be modified but its address in memory will not change */ -/* ============================================================ */ -static void -mb_sanitize(char * s) +/* ======================================================================= */ +/* Callback function used by tst_traverse */ +/* Iterate the linked list attached to the string containing the index of */ +/* the words in the input flow. Each page number is then used to determine */ +/* the lower page greater than the cursor position */ +/* ----------------------------------------------------------------------- */ +/* This is a special version of tst_cb which permit to find the first word */ +/* ----------------------------------------------------------------------- */ +/* Require new_current to be set to count - 1 at start */ +/* Update new_current to the smallest greater position than current */ +/* ======================================================================= */ +int +tst_cb_cli(void * elem) { - char * p = s; - int n; - size_t len; + long n = 0; + int rc = 0; - len = strlen(s); - while (*p) - { - n = mb_get_length(*p); - if (n > 1) - { - *p = '.'; - memmove(p + 1, p + n, len - (p - s) - n + 1); - len -= (n - 1); - } - p++; - } -} + /* The data attached to the string in the tst is a linked list of position */ + /* of the string in the input flow, This list is naturally sorted */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + ll_t * list = (ll_t *)elem; -static const char trailing_bytes_for_utf8[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 -}; - -/* ================================================================= */ -/* UTF8 validation routine inspired by Jeff Bezanson */ -/* placed in the public domain Fall 2005 */ -/* (https://github.com/JeffBezanson/cutef8) */ -/* */ -/* Returns 1 if str contains a valid UTF8 byte sequence, 0 otherwise */ -/* ================================================================= */ -static int -mb_validate(const char * str, size_t length) -{ - const unsigned char *p, *pend = (const unsigned char *)str + length; - unsigned char c; - size_t ab; + ll_node_t * node = list->head; - for (p = (const unsigned char *)str; p < pend; p++) + while (n++ < list->len) { - c = *p; - if (c < 128) - continue; - if ((c & 0xc0) != 0xc0) - return 0; - ab = trailing_bytes_for_utf8[c]; - if (length < ab) - return 0; - length -= ab; - - p++; - /* Check top bits in the second byte */ - /* """"""""""""""""""""""""""""""""" */ - if ((*p & 0xc0) != 0x80) - return 0; - - /* Check for overlong sequences for each different length */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ - switch (ab) - { - /* Check for xx00 000x */ - /* """"""""""""""""""" */ - case 1: - if ((c & 0x3e) == 0) - return 0; - continue; /* We know there aren't any more bytes to check */ - - /* Check for 1110 0000, xx0x xxxx */ - /* """""""""""""""""""""""""""""" */ - case 2: - if (c == 0xe0 && (*p & 0x20) == 0) - return 0; - break; + long pos; - /* Check for 1111 0000, xx00 xxxx */ - /* """""""""""""""""""""""""""""" */ - case 3: - if (c == 0xf0 && (*p & 0x30) == 0) - return 0; - break; + pos = *(long *)(node->data); - /* Check for 1111 1000, xx00 0xxx */ - /* """""""""""""""""""""""""""""" */ - case 4: - if (c == 0xf8 && (*p & 0x38) == 0) - return 0; - break; + word_a[pos].is_matching = 1; + insert_sorted_index(&matching_words_a, &matching_words_a_size, + &matches_count, pos); - /* Check for leading 0xfe or 0xff, */ - /* and then for 1111 1100, xx00 00xx */ - /* """"""""""""""""""""""""""""""""" */ - case 5: - if (c == 0xfe || c == 0xff || (c == 0xfc && (*p & 0x3c) == 0)) - return 0; - break; - } + /* We already are at the last word, report the finding */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + if (pos == count - 1) + return 1; - /* Check for valid bytes after the 2nd, if any; all must start 10 */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - while (--ab > 0) + /* Only consider the indexes above the current cursor position */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (pos >= current) /* Enable the search of the current word */ { - if ((*(++p) & 0xc0) != 0x80) - return 0; + /* As the future new current index has been set to the highest possible */ + /* value, each new possible position can only improve the estimation */ + /* we set rc to 1 to mark that */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (pos < new_current) + { + new_current = pos; + rc = 1; + } } - } - - return 1; -} -/* ========================================================================= */ -/* Allocate memory and safely concatenate strings. Stolen from a public */ -/* domain implementation which can be found here: */ -/* http://openwall.info/wiki/people/solar/software/public-domain-source-code */ -/* ========================================================================= */ -static char * -concat(const char * s1, ...) -{ - va_list args; - const char * s; - char * p, *result; - size_t l, m, n; - - m = n = strlen(s1); - va_start(args, s1); - while ((s = va_arg(args, char *))) - { - l = strlen(s); - if ((m += l) < l) - break; - } - va_end(args); - if (s || m >= INT_MAX) - return NULL; - - result = (char *)xmalloc(m + 1); - - memcpy(p = result, s1, n); - p += n; - va_start(args, s1); - while ((s = va_arg(args, char *))) - { - l = strlen(s); - if ((n += l) < l || n > m) - break; - memcpy(p, s, l); - p += l; - } - va_end(args); - if (s || m != n || p != result + n) - { - free(result); - return NULL; + node = node->next; } - - *p = 0; - return result; + return rc; } +/* *************** */ +/* Input functions */ +/* *************** */ + /* =============================================== */ -/* Is the string str2 a prefix of the string str1? */ +/* Non delay reading of a scancode. */ +/* Update a scancodes buffer and return its length */ +/* in bytes. */ /* =============================================== */ -static int -strprefix(char * str1, char * str2) +int +get_scancode(unsigned char * s, size_t max) { - while (*str1 != '\0' && *str1 == *str2) - { - str1++; - str2++; - } + int c; + size_t i = 1; + struct termios original_ts, nowait_ts; - return *str2 == '\0'; -} + if ((c = my_fgetc(stdin)) == EOF) + return 0; -/* ====================== */ -/* Multibyte UTF-8 strlen */ -/* ====================== */ -static size_t -mb_strlen(char * str) -{ - size_t i = 0, j = 0; + /* Initialize the string with the first byte */ + /* """"""""""""""""""""""""""""""""""""""""" */ + memset(s, '\0', max); + s[0] = c; - while (str[i]) + /* 0x1b (ESC) has been found, proceed to check if additional codes */ + /* are available */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (c == 0x1b || c > 0x80) { - if ((str[i] & 0xc0) != 0x80) - j++; - i++; - } - return j; -} - -/* ====================================================================== */ -/* Multibytes extraction of the prefix of n multibyte chars from a string */ -/* The destination string d must have been allocated before. */ -/* pos is updated to reflect the position AFTER the prefix. */ -/* ====================================================================== */ -static char * -mb_strprefix(char * d, char * s, long n, long * pos) -{ - long i = 0; - long j = 0; + /* Save the terminal parameters and configure getchar() */ + /* to return immediately */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ + tcgetattr(0, &original_ts); + nowait_ts = original_ts; + nowait_ts.c_lflag &= ~ISIG; + nowait_ts.c_cc[VMIN] = 0; + nowait_ts.c_cc[VTIME] = 0; + tcsetattr(0, TCSADRAIN, &nowait_ts); - *pos = 0; + /* Check if additional code is available after 0x1b */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + if ((c = my_fgetc(stdin)) != EOF) + { + s[1] = c; - while (s[i] && j < n) - { - d[i] = s[i]; - i++; - j++; - while (s[i] && (s[i] & 0xC0) == 0x80) + i = 2; + while (i < max && (c = my_fgetc(stdin)) != EOF) + s[i++] = c; + } + else { - d[i] = s[i]; - i++; + /* There isn't a new code, this mean 0x1b came from ESC key */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ } - } - *pos = i; + /* Restore the save terminal parameters */ + /* """""""""""""""""""""""""""""""""""" */ + tcsetattr(0, TCSADRAIN, &original_ts); - d[i] = '\0'; + /* Ignore EOF when a scancode contains an escape sequence. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ + clearerr(stdin); + } - return d; + return i; } -/* =========================================================== */ -/* Convert a multibyte (UTF-8) char string to a wchar_t string */ -/* =========================================================== */ -static wchar_t * -mb_strtowcs(char * s) +/* ===================================================================== */ +/* Get bytes from stdin. If the first byte is the leading character of a */ +/* UTF-8 glyph, the following ones are also read. */ +/* The utf8_get_length function is used to get the number of bytes of */ +/* the character. */ +/* ===================================================================== */ +int +get_bytes(FILE * input, char * utf8_buffer, langinfo_t * langinfo) { - int converted = 0; - unsigned char * ch; - wchar_t * wptr, *w; - size_t size; - - size = (long)strlen(s); - w = xmalloc((size + 1) * sizeof(wchar_t)); - w[0] = L'\0'; - - wptr = w; - for (ch = (unsigned char *)s; *ch; ch += converted) - { - if ((converted = mbtowc(wptr, (char *)ch, 4)) > 0) - wptr++; - else - { - *wptr++ = (wchar_t)*ch; - converted = 1; - } - } + int byte; + int last = 0; + int n; - *wptr = L'\0'; + /* Read the first byte */ + /* """"""""""""""""""" */ + byte = my_fgetc(input); - return w; -} + if (byte == EOF) + return EOF; -/* *************************************************************** */ -/* Ternary Search Tree functions */ -/* Inspired by: https://www.cs.princeton.edu/~rs/strings/tstdemo.c */ -/* *************************************************************** */ + utf8_buffer[last++] = byte; -/* ====================================== */ -/* Ternary search tree insertion function */ -/* ====================================== */ -static tst_node_t * -tst_insert(tst_node_t * p, wchar_t * w, void * data) -{ - if (p == NULL) + /* Check if we need to read more bytes to form a sequence */ + /* and put the number of bytes of the sequence in last. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (langinfo->utf8 && ((n = utf8_get_length(byte)) > 1)) { - p = (tst_node_t *)xmalloc(sizeof(tst_node_t)); - p->splitchar = *w; - p->lokid = p->eqkid = p->hikid = NULL; - p->data = NULL; + while (last < n && (byte = my_fgetc(input)) != EOF && (byte & 0xc0) == 0x80) + utf8_buffer[last++] = byte; + + if (byte == EOF) + return EOF; } - if (*w < p->splitchar) - p->lokid = tst_insert(p->lokid, w, data); - else if (*w == p->splitchar) + utf8_buffer[last] = '\0'; + + /* Replace an invalid UTF-8 byte sequence by a single dot. */ + /* In this case the original sequence is lost (unsupported */ + /* encoding). */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (langinfo->utf8 && !utf8_validate(utf8_buffer, last)) { - if (*w == L'\0') - { - p->data = data; - p->eqkid = NULL; - } - else - p->eqkid = tst_insert(p->eqkid, ++w, data); + byte = utf8_buffer[0] = '.'; + utf8_buffer[1] = '\0'; } - else - p->hikid = tst_insert(p->hikid, w, data); - return (p); + return byte; } -#if 0 /* here for coherency but not used. */ -/* ===================================== */ -/* Ternary search tree deletion function */ -/* user data area not cleaned */ -/* ===================================== */ -void -tst_cleanup(tst_node_t * p) +/* ==========================================================================*/ +/* Expand the string str by replacing all its embedded special characters by */ +/* their corresponding escape sequence */ +/* dest must be long enough to contain the expanded string */ +/* ========================================================================= */ +size_t +expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggle) { - if (p) + char c; + int n; + int all_spaces = 1; + char * ptr = dest; + size_t len = 0; + + while ((c = *(src++))) { - tst_cleanup(p->lokid); - if (p->splitchar) - tst_cleanup(p->eqkid); - tst_cleanup(p->hikid); - free(p); - } -} -#endif + /* UTF-8 codepoints take more than on character */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + if ((n = utf8_get_length(c)) > 1) + { + all_spaces = 0; -/* ========================================================== */ -/* Recursive traversal of a ternary tree. A callback function */ -/* is also called when a complete string is found */ -/* returns 1 if the callback function succeed (returned 1) at */ -/* least once */ -/* the first_call argument is for initializing the static */ -/* variable */ -/* ========================================================== */ -static int -tst_traverse(tst_node_t * p, int (*callback)(void *), int first_call) -{ - static int rc; + if (langinfo->utf8) + /* If the locale is UTF-8 aware, copy src into ptr. */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + do + { + *(ptr++) = c; + len++; + } while (--n && (c = *(src++))); + else + { + /* If not, ignore the bytes composing the UTF-8 */ + /* glyph and replace them with a single '.'. */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + do + { + /* Skip this byte. */ + /* ''''''''''''''' */ + } while (--n && ('\0' != *(src++))); + + *(ptr++) = '.'; + len++; + } + } + else + /* This is not an UTF-8 glyph */ + /* """""""""""""""""""""""""" */ + switch (c) + { + case '\a': + *(ptr++) = '\\'; + *(ptr++) = 'a'; + len += 2; + all_spaces = 0; + break; + case '\b': + *(ptr++) = '\\'; + *(ptr++) = 'b'; + len += 2; + all_spaces = 0; + break; + case '\t': + *(ptr++) = '\\'; + *(ptr++) = 't'; + len += 2; + all_spaces = 0; + break; + case '\n': + *(ptr++) = '\\'; + *(ptr++) = 'n'; + len += 2; + all_spaces = 0; + break; + case '\v': + *(ptr++) = '\\'; + *(ptr++) = 'v'; + len += 2; + all_spaces = 0; + break; + case '\f': + *(ptr++) = '\\'; + *(ptr++) = 'f'; + len += 2; + all_spaces = 0; + break; + case '\r': + *(ptr++) = '\\'; + *(ptr++) = 'r'; + len += 2; + all_spaces = 0; + break; + case '\\': + *(ptr++) = '\\'; + *(ptr++) = '\\'; + len += 2; + all_spaces = 0; + break; + default: + if (my_isprint(c)) + { + *(ptr++) = c; + all_spaces = 0; + } + else + { + if (toggle->blank_nonprintable) + *(ptr++) = ' '; + else + { + *(ptr++) = '.'; + all_spaces = 0; + } + } + len++; + } + } - if (first_call) - rc = 0; + /* If the word contains only spaces, replace them */ + /* by underscores so that it can be seen */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + if (all_spaces) + memset(dest, ' ', len); - if (!p) - return 0; - tst_traverse(p->lokid, callback, 0); - if (p->splitchar != L'\0') - tst_traverse(p->eqkid, callback, 0); - else - rc += (*callback)(p->data); - tst_traverse(p->hikid, callback, 0); + *ptr = '\0'; /* Ensure that dest has a nul terminator */ - return !!rc; + return len; } -/* ================================================================ */ -/* Callback to add a word index in the sorted list of matched words */ -/* ================================================================ */ -static int -set_matching_flag(void * elem) +/* ===================================================================== */ +/* get_word(input): return a char pointer to the next word (as a string) */ +/* Accept: a FILE * for the input stream */ +/* Return: a char * */ +/* On Success: the return value will point to a nul-terminated string */ +/* On Failure: the return value will be set to NULL */ +/* ===================================================================== */ +char * +get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list, + char * utf8_buffer, unsigned char * is_last, toggle_t * toggle, + langinfo_t * langinfo, win_t * win, limits_t * limits) { - ll_t * list = (ll_t *)elem; + char * temp = NULL; + int byte; + long utf8_count = 0; /* count chars used in current allocation */ + long wordsize; /* size of current allocation in chars */ + int is_dquote; /* double quote presence indicator */ + int is_squote; /* single quote presence indicator */ + int is_special; /* a character is special after a \ */ - ll_node_t * node = list->head; + /* Skip leading delimiters */ + /* """"""""""""""""""""""" */ + byte = get_bytes(input, utf8_buffer, langinfo); - while (node) + while (byte == EOF + || ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL) { - size_t pos; - - pos = *(size_t *)(node->data); - if (word_a[pos].is_selectable) - word_a[pos].is_matching = 1; - - insert_sorted_index(&matching_words_a, &matching_words_a_size, - &matches_count, pos); + if (byte == EOF) + return NULL; - node = node->next; + byte = get_bytes(input, utf8_buffer, langinfo); } - return 1; -} - -/* ======================================================================= */ -/* Traverse the word tst looking for a wchar and build a list of pointers */ -/* containing all the sub-tst * nodes after these node potentially leading */ -/* to words containing the next wchar os the search string */ -/* ======================================================================= */ -static int -tst_substring_traverse(tst_node_t * p, int (*callback)(void *), int first_call, - wchar_t w) -{ - static int rc; - if (first_call) - rc = 0; + /* Allocate initial word storage space */ + /* """"""""""""""""""""""""""""""""""" */ + temp = xmalloc(wordsize = CHARSCHUNK); - if (!p) - return 0; + /* Start stashing bytes. Stop when we meet a non delimiter or EOF */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + utf8_count = 0; + is_dquote = 0; + is_squote = 0; + is_special = 0; - if (p->splitchar == w) + while (byte != EOF) { - ll_node_t * node; - sub_tst_t * sub_tst_data; - - node = tst_search_list->tail; - sub_tst_data = (sub_tst_t *)(node->data); - - if (p->eqkid) - insert_sorted_ptr(&(sub_tst_data->array), &(sub_tst_data->size), - &(sub_tst_data->count), p->eqkid); + size_t i = 0; - rc = 1; - } - - tst_substring_traverse(p->lokid, callback, 0, w); - if (p->splitchar != L'\0') - tst_substring_traverse(p->eqkid, callback, 0, w); - else if (callback != NULL) - rc += (*callback)(p->data); - tst_substring_traverse(p->hikid, callback, 0, w); + if (utf8_count >= limits->word_length) + { + fprintf(stderr, + "The length of a word has reached the limit of " + "%ld characters.\n", + limits->word_length); - return !!rc; -} + exit(EXIT_FAILURE); + } -/* ======================================================================= */ -/* Traverse the word tst looking for a wchar and build a list of pointers */ -/* containing all the sub-tst * nodes after these node potentially leading */ -/* to words containing the next wchar os the search string */ -/* ======================================================================= */ -static int -tst_fuzzy_traverse(tst_node_t * p, int (*callback)(void *), int first_call, - wchar_t w) -{ - static int rc; - wchar_t w1s[2]; - wchar_t w2s[2]; + if (byte == '\\' && !is_special) + { + is_special = 1; + goto next; + } - w1s[1] = w2s[1] = L'\0'; + /* Parse special characters */ + /* """""""""""""""""""""""" */ + if (is_special) + switch (byte) + { + case 'a': + utf8_buffer[0] = byte = '\a'; + utf8_buffer[1] = '\0'; + break; - if (first_call) - rc = 0; + case 'b': + utf8_buffer[0] = byte = '\b'; + utf8_buffer[1] = '\0'; + break; - if (!p) - return 0; + case 't': + utf8_buffer[0] = byte = '\t'; + utf8_buffer[1] = '\0'; + break; - w1s[0] = p->splitchar; - w2s[0] = w; + case 'n': + utf8_buffer[0] = byte = '\n'; + utf8_buffer[1] = '\0'; + break; - if (wcscasecmp(w1s, w2s) == 0) - { - ll_node_t * node; - sub_tst_t * sub_tst_data; + case 'v': + utf8_buffer[0] = byte = '\v'; + utf8_buffer[1] = '\0'; + break; - node = tst_search_list->tail; - sub_tst_data = (sub_tst_t *)(node->data); + case 'f': + utf8_buffer[0] = byte = '\f'; + utf8_buffer[1] = '\0'; + break; - if (p->eqkid != NULL) - insert_sorted_ptr(&(sub_tst_data->array), &(sub_tst_data->size), - &(sub_tst_data->count), p->eqkid); + case 'r': + utf8_buffer[0] = byte = '\r'; + utf8_buffer[1] = '\0'; + break; - rc += 1; - } + case 'u': + utf8_buffer[0] = '\\'; + utf8_buffer[1] = 'u'; + utf8_buffer[2] = '\0'; + break; - tst_fuzzy_traverse(p->lokid, callback, 0, w); - if (p->splitchar != L'\0') - tst_fuzzy_traverse(p->eqkid, callback, 0, w); - else if (callback != NULL) - rc += (*callback)(p->data); - tst_fuzzy_traverse(p->hikid, callback, 0, w); + case '\\': + utf8_buffer[0] = byte = '\\'; + utf8_buffer[1] = '\0'; + break; + } + else + { + /* Manage double quotes */ + /* """""""""""""""""""" */ + if (byte == '"' && !is_squote) + is_dquote = !is_dquote; - return !!rc; -} + /* Manage single quotes */ + /* """""""""""""""""""" */ + if (byte == '\'' && !is_dquote) + is_squote = !is_squote; + } -/* =================================================================== */ -/* Search a complete string in a Ternary tree staring from a root node */ -/* =================================================================== */ -static void * -tst_search(tst_node_t * root, wchar_t * w) -{ - tst_node_t * p; + /* Only consider delimiters when outside quotations */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + if ((!is_dquote && !is_squote) + && ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL) + break; - p = root; + /* We no dot count the significant quotes */ + /* """""""""""""""""""""""""""""""""""""" */ + if (!is_special + && ((byte == '"' && !is_squote) || (byte == '\'' && !is_dquote))) + { + is_special = 0; + goto next; + } - while (p) - { - if (*w < p->splitchar) - p = p->lokid; - else if (*w == p->splitchar) + /* Feed temp with the content of utf8_buffer */ + /* """"""""""""""""""""""""""""""""""""""""" */ + while (utf8_buffer[i] != '\0') { - if (*w++ == L'\0') - return p->data; - p = p->eqkid; + if (utf8_count >= wordsize - 1) + temp = xrealloc(temp, + wordsize += (utf8_count / CHARSCHUNK + 1) * CHARSCHUNK); + + *(temp + utf8_count++) = utf8_buffer[i]; + i++; } - else - p = p->hikid; + + is_special = 0; + + next: + byte = get_bytes(input, utf8_buffer, langinfo); } - return NULL; -} + /* Nul-terminate the word to make it a string */ + /* """""""""""""""""""""""""""""""""""""""""" */ + *(temp + utf8_count) = '\0'; -/* =============================================================== */ -/* Search all strings beginning with the same prefix */ -/* the callback function will be applied to each of theses strings */ -/* returns NULL if no string matched the prefix */ -/* =============================================================== */ -static void * -tst_prefix_search(tst_node_t * root, wchar_t * w, int (*callback)(void *)) -{ - tst_node_t * p = root; - size_t len = wcslen(w); - size_t rc; + /* Replace the UTF-8 ASCII representations in the word just */ + /* read by their binary values. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + utf8_interpret(temp, langinfo); - while (p) + /* Skip all field delimiters before a record delimiter */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + if (ll_find(record_delims_list, utf8_buffer, delims_cmp) == NULL) { - if (*w < p->splitchar) - p = p->lokid; - else if (*w == p->splitchar) + byte = get_bytes(input, utf8_buffer, langinfo); + while (byte != EOF + && ll_find(word_delims_list, utf8_buffer, delims_cmp) != NULL + && ll_find(record_delims_list, utf8_buffer, delims_cmp) == NULL) + byte = get_bytes(input, utf8_buffer, langinfo); + + if (langinfo->utf8 && utf8_get_length(utf8_buffer[0]) > 1) { - len--; - if (*w++ == L'\0') - return p->data; - if (len == 0) - { - rc = tst_traverse(p->eqkid, callback, 1); - return (void *)(long)rc; - } - p = p->eqkid; + size_t pos; + + pos = strlen(utf8_buffer); + while (pos > 0) + my_ungetc(utf8_buffer[--pos]); } else - p = p->hikid; + my_ungetc(byte); } - return NULL; + /* Mark it as the last word of a record if its sequence matches a */ + /* record delimiter except in tab mode */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (byte == EOF + || ((win->col_mode || win->line_mode) + && ll_find(record_delims_list, utf8_buffer, delims_cmp) != NULL)) + *is_last = 1; + else + *is_last = 0; + + /* Remove the ANSI color escape sequences from the word */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ + strip_ansi_color(temp, toggle); + + return temp; } -/* ======================================================================= */ -/* Callback function used by tst_traverse */ -/* Iterate the linked list attached to the string containing the index of */ -/* the words in the input flow. Each page number is then added in a sorted */ -/* array avoiding duplications keeping the array sorted. */ -/* Mark the identified words as a matching word. */ -/* ======================================================================= */ -static int -tst_cb(void * elem) +/* ========================================================= */ +/* Set a foreground color according to terminal capabilities */ +/* ========================================================= */ +void +set_foreground_color(term_t * term, short color) { - /* The data attached to the string in the tst is a linked list of position */ - /* of the string in the input flow, This list is naturally sorted */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - ll_t * list = (ll_t *)elem; - - ll_node_t * node = list->head; + if (term->color_method == 0 && term->has_setf) + tputs(TPARM2(set_foreground, color), 1, outch); + else if (term->color_method == 1 && term->has_setaf) + tputs(TPARM2(set_a_foreground, color), 1, outch); +} - while (node) - { - size_t pos; +/* ========================================================= */ +/* Set a background color according to terminal capabilities */ +/* ========================================================= */ +void +set_background_color(term_t * term, short color) +{ + if (term->color_method == 0 && term->has_setb) + tputs(TPARM2(set_background, color), 1, outch); + else if (term->color_method == 1 && term->has_setab) + tputs(TPARM2(set_a_background, color), 1, outch); +} - pos = *(long *)(node->data); +/* ====================================================== */ +/* Put a scrolling symbol at the first column of the line */ +/* ====================================================== */ +void +left_margin_putp(char * s, term_t * term, win_t * win) +{ + apply_attr(term, win->shift_attr); - word_a[pos].is_matching = 1; - insert_sorted_index(&matching_words_a, &matching_words_a_size, - &matches_count, pos); + /* We won't print this symbol when not in column mode */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + if (*s != '\0') + fputs(s, stdout); - node = node->next; - } - return 1; + tputs(TPARM1(exit_attribute_mode), 1, outch); } -/* ======================================================================= */ -/* Callback function used by tst_traverse */ -/* Iterate the linked list attached to the string containing the index of */ -/* the words in the input flow. Each page number is then used to determine */ -/* the lower page greater than the cursor position */ -/* ----------------------------------------------------------------------- */ -/* This is a special version of tst_cb which permit to find the first word */ -/* ----------------------------------------------------------------------- */ -/* Require new_current to be set to count - 1 at start */ -/* Update new_current to the smallest greater position than current */ -/* ======================================================================= */ -static int -tst_cb_cli(void * elem) +/* ===================================================== */ +/* Put a scrolling symbol at the last column of the line */ +/* ===================================================== */ +void +right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term, + win_t * win, long line, long offset) { - long n = 0; - int rc = 0; - - /* The data attached to the string in the tst is a linked list of position */ - /* of the string in the input flow, This list is naturally sorted */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - ll_t * list = (ll_t *)elem; - - ll_node_t * node = list->head; + apply_attr(term, win->bar_attr); - while (n++ < list->len) + if (term->has_hpa) + tputs(TPARM2(column_address, offset + win->max_width + 1), 1, outch); + else if (term->has_cursor_address) + tputs(TPARM3(cursor_address, term->curs_line + line - 2, + offset + win->max_width + 1), + 1, outch); + else if (term->has_parm_right_cursor) { - long pos; - - pos = *(long *)(node->data); - - word_a[pos].is_matching = 1; - insert_sorted_index(&matching_words_a, &matching_words_a_size, - &matches_count, pos); + fputc('\r', stdout); + tputs(TPARM2(parm_right_cursor, offset + win->max_width + 1), 1, outch); + } + else + { + long i; - /* We already are at the last word, report the finding */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - if (pos == count - 1) - return 1; + fputc('\r', stdout); + for (i = 0; i < offset + win->max_width + 1; i++) + tputs(TPARM1(cursor_right), 1, outch); + } - /* Only consider the indexes above the current cursor position */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (pos >= current) /* Enable the search of the current word */ - { - /* As the future new current index has been set to the highest possible */ - /* value, each new possible position can only improve the estimation */ - /* we set rc to 1 to mark that */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (pos < new_current) - { - new_current = pos; - rc = 1; - } - } + if (langinfo->utf8) + fputs(s1, stdout); + else + fputs(s2, stdout); - node = node->next; - } - return rc; + tputs(TPARM1(exit_attribute_mode), 1, outch); } -/* *************** */ -/* Input functions */ -/* *************** */ +/* ************** */ +/* Core functions */ +/* ************** */ -/* =============================================== */ -/* Non delay reading of a scancode. */ -/* Update a scancodes buffer and return its length */ -/* in bytes. */ -/* =============================================== */ -static int -get_scancode(unsigned char * s, size_t max) +/* ============================================================== */ +/* Split the lines of the message given to -m to a linked list of */ +/* lines. */ +/* Also fill the maximum screen width and the maximum number */ +/* of bytes of the longest line */ +/* ============================================================== */ +void +get_message_lines(char * message, ll_t * message_lines_list, + long * message_max_width, long * message_max_len) { - int c; - size_t i = 1; - struct termios original_ts, nowait_ts; - - if ((c = my_fgetc(stdin)) == EOF) - return 0; + char * str; + char * ptr; + char * cr_ptr; + long n; + wchar_t * w = NULL; - /* Initialize the string with the first byte */ - /* """"""""""""""""""""""""""""""""""""""""" */ - memset(s, '\0', max); - s[0] = c; + *message_max_width = 0; + *message_max_len = 0; + ptr = message; - /* 0x1b (ESC) has been found, proceed to check if additional codes */ - /* are available */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (c == 0x1b || c > 0x80) + /* For each line terminated with a EOL character */ + /* """"""""""""""""""""""""""""""""""""""""""""" */ + while (*ptr != '\0' && (cr_ptr = strchr(ptr, '\n')) != NULL) { - /* Save the terminal parameters and configure getchar() */ - /* to return immediately */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ - tcgetattr(0, &original_ts); - nowait_ts = original_ts; - nowait_ts.c_lflag &= ~ISIG; - nowait_ts.c_cc[VMIN] = 0; - nowait_ts.c_cc[VTIME] = 0; - tcsetattr(0, TCSADRAIN, &nowait_ts); - - /* Check if additional code is available after 0x1b */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - if ((c = my_fgetc(stdin)) != EOF) + if (cr_ptr > ptr) { - s[1] = c; - - i = 2; - while (i < max && (c = my_fgetc(stdin)) != EOF) - s[i++] = c; + str = xmalloc(cr_ptr - ptr + 1); + str[cr_ptr - ptr] = '\0'; + memcpy(str, ptr, cr_ptr - ptr); } else - { - /* There isn't a new code, this mean 0x1b came from ESC key */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - } + str = xstrdup(""); - /* Restore the save terminal parameters */ - /* """""""""""""""""""""""""""""""""""" */ - tcsetattr(0, TCSADRAIN, &original_ts); + ll_append(message_lines_list, str); - /* Ignore EOF when a scancode contains an escape sequence. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ - clearerr(stdin); - } + /* If needed, update the message maximum width */ + /* """"""""""""""""""""""""""""""""""""""""""" */ + n = wcswidth((w = utf8_strtowcs(str)), utf8_strlen(str)); + free(w); - return i; -} + if (n > *message_max_width) + *message_max_width = n; -/* ===================================================================== */ -/* Get bytes from stdin. If the first byte is the leading character of a */ -/* multibyte one, the following ones a also read */ -/* The mb_get_length function is used to get the number of bytes of the */ -/* character. */ -/* ===================================================================== */ -static int -get_bytes(FILE * input, char * mb_buffer, langinfo_t * langinfo) -{ - int byte; - int last = 0; - int n; + /* If needed, update the message maximum number */ + /* of bytes used by the longest line */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + if ((n = (long)strlen(str)) > *message_max_len) + *message_max_len = n; - /* Read the first byte */ - /* """"""""""""""""""" */ - byte = my_fgetc(input); + ptr = cr_ptr + 1; + } - if (byte == EOF) - return EOF; + /* For the last line */ + /* """"""""""""""""" */ + if (*ptr != '\0') + { + ll_append(message_lines_list, xstrdup(ptr)); - mb_buffer[last++] = byte; + n = wcswidth((w = utf8_strtowcs(ptr)), utf8_strlen(ptr)); + free(w); - /* Check if we need to read more bytes to form a sequence */ - /* and put the number of bytes of the sequence in last. */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (langinfo->utf8 && ((n = mb_get_length(byte)) > 1)) - { - while (last < n && (byte = my_fgetc(input)) != EOF && (byte & 0xc0) == 0x80) - mb_buffer[last++] = byte; + if (n > *message_max_width) + *message_max_width = n; - if (byte == EOF) - return EOF; + /* If needed, update the message maximum number */ + /* of bytes used by the longest line */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + if ((n = (long)strlen(ptr)) > *message_max_len) + *message_max_len = n; } + else + ll_append(message_lines_list, xstrdup("")); +} - mb_buffer[last] = '\0'; +/* =================================================================== */ +/* Set the new start and the new end of the window structure according */ +/* to the current cursor position. */ +/* =================================================================== */ +void +set_win_start_end(win_t * win, long current, long last) +{ + long cur_line, end_line; - /* Replace an invalid UTF-8 byte sequence by a single dot. */ - /* In this case the original sequence is lost (unsupported */ - /* encoding). */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (langinfo->utf8 && !mb_validate(mb_buffer, last)) + cur_line = line_nb_of_word_a[current]; + if (cur_line == last) + win->end = count - 1; + else { - byte = mb_buffer[0] = '.'; - mb_buffer[1] = '\0'; + /* In help mode we must not modify the windows start/end position as */ + /* It must be redrawn exactly as it was before. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!help_mode) + { + if (cur_line + win->max_lines / 2 + 1 <= last) + win->end = first_word_in_line_a[cur_line + win->max_lines / 2 + 1] - 1; + else + win->end = first_word_in_line_a[last]; + } } + end_line = line_nb_of_word_a[win->end]; - return byte; + if (end_line < win->max_lines) + win->start = 0; + else + win->start = first_word_in_line_a[end_line - win->max_lines + 1]; } -/* ==========================================================================*/ -/* Expand the string str by replacing all its embedded special characters by */ -/* their corresponding escape sequence */ -/* dest must be long enough to contain the expanded string */ /* ========================================================================= */ -static size_t -expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggle) +/* Set the metadata associated with a word, its starting and ending position */ +/* the line in which it is put and so on. */ +/* Set win.start win.end and the starting and ending position of each word. */ +/* This function is only called initially, when resizing the terminal and */ +/* potentially when the search function is used. */ +/* ========================================================================= */ +long +build_metadata(term_t * term, long count, win_t * win) { - char c; - int n; - int all_spaces = 1; - char * ptr = dest; - size_t len = 0; + long i = 0; + long word_len; + long len = 0; + long last = 0; + long word_width; + long tab_count; /* Current number of words in the line, * + * used in tab_mode */ + wchar_t * w; - while ((c = *(src++))) + line_nb_of_word_a[0] = 0; + first_word_in_line_a[0] = 0; + + /* In column mode we need to calculate win->max_width, first initialize */ + /* it to 0 and increment it later in the loop */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!win->col_mode) + win->max_width = 0; + + tab_count = 0; + while (i < count) { - /* UTF-8 codepoints take more than on character */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - if ((n = mb_get_length(c)) > 1) - { - all_spaces = 0; + /* Determine the number of screen positions used by the word */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + word_len = mbstowcs(NULL, word_a[i].str, 0); + word_width = wcswidth((w = utf8_strtowcs(word_a[i].str)), word_len); - if (langinfo->utf8) - /* If the locale is UTF-8 aware, copy src into ptr. */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - do - { - *(ptr++) = c; - len++; - } while (--n && (c = *(src++))); - else + /* Manage the case where the word is larger than the terminal width */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (word_width >= term->ncolumns - 2) + { + /* Shorten the word until it fits */ + /* """""""""""""""""""""""""""""" */ + do { - /* If not, ignore the bytes composing the multibyte */ - /* character and replace them with a single '.'. */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - do - { - /* Skip this byte. */ - /* ''''''''''''''' */ - } while (--n && ('\0' != *(src++))); + word_width = wcswidth(w, word_len--); + } while (word_len > 0 && word_width >= term->ncolumns - 2); + } + free(w); - *(ptr++) = '.'; - len++; - } - } - else - /* This is not a multibyte character */ - /* """"""""""""""""""""""""""""""""" */ - switch (c) - { - case '\a': - *(ptr++) = '\\'; - *(ptr++) = 'a'; - len += 2; - all_spaces = 0; - break; - case '\b': - *(ptr++) = '\\'; - *(ptr++) = 'b'; - len += 2; - all_spaces = 0; - break; - case '\t': - *(ptr++) = '\\'; - *(ptr++) = 't'; - len += 2; - all_spaces = 0; - break; - case '\n': - *(ptr++) = '\\'; - *(ptr++) = 'n'; - len += 2; - all_spaces = 0; - break; - case '\v': - *(ptr++) = '\\'; - *(ptr++) = 'v'; - len += 2; - all_spaces = 0; - break; - case '\f': - *(ptr++) = '\\'; - *(ptr++) = 'f'; - len += 2; - all_spaces = 0; - break; - case '\r': - *(ptr++) = '\\'; - *(ptr++) = 'r'; - len += 2; - all_spaces = 0; - break; - case '\\': - *(ptr++) = '\\'; - *(ptr++) = '\\'; - len += 2; - all_spaces = 0; - break; - default: - if (my_isprint(c)) - { - *(ptr++) = c; - all_spaces = 0; - } - else - { - if (toggle->blank_nonprintable) - *(ptr++) = ' '; - else - { - *(ptr++) = '.'; - all_spaces = 0; - } - } - len++; - } - } - - /* If the word contains only spaces, replace them */ - /* by underscores so that it can be seen */ - /* """""""""""""""""""""""""""""""""""""""""""""" */ - if (all_spaces) - memset(dest, ' ', len); - - *ptr = '\0'; /* Ensure that dest has a nul terminator */ - - return len; -} - -/* ===================================================================== */ -/* get_word(input): return a char pointer to the next word (as a string) */ -/* Accept: a FILE * for the input stream */ -/* Return: a char * */ -/* On Success: the return value will point to a nul-terminated string */ -/* On Failure: the return value will be set to NULL */ -/* ===================================================================== */ -static char * -get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list, - char * mb_buffer, unsigned char * is_last, toggle_t * toggle, - langinfo_t * langinfo, win_t * win, limits_t * limits) -{ - char * temp = NULL; - int byte; - long mb_count = 0; /* count chars used in current allocation */ - long wordsize; /* size of current allocation in chars */ - int is_dquote; /* double quote presence indicator */ - int is_squote; /* single quote presence indicator */ - int is_special; /* a character is special after a \ */ - - /* Skip leading delimiters */ - /* """"""""""""""""""""""" */ - byte = get_bytes(input, mb_buffer, langinfo); - - while (byte == EOF - || ll_find(word_delims_list, mb_buffer, delims_cmp) != NULL) - { - if (byte == EOF) - return NULL; - - byte = get_bytes(input, mb_buffer, langinfo); - } - - /* Allocate initial word storage space */ - /* """"""""""""""""""""""""""""""""""" */ - temp = xmalloc(wordsize = CHARSCHUNK); - - /* Start stashing bytes. Stop when we meet a non delimiter or EOF */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_count = 0; - is_dquote = 0; - is_squote = 0; - is_special = 0; - - while (byte != EOF) - { - size_t i = 0; - - if (mb_count >= limits->word_length) - { - fprintf(stderr, - "A word's length has reached the limit " - "(%ld), exiting.\n", - limits->word_length); - - exit(EXIT_FAILURE); - } - - if (byte == '\\' && !is_special) - { - is_special = 1; - goto next; - } - - /* Parse special characters */ - /* """""""""""""""""""""""" */ - if (is_special) - switch (byte) - { - case 'a': - mb_buffer[0] = byte = '\a'; - mb_buffer[1] = '\0'; - break; - - case 'b': - mb_buffer[0] = byte = '\b'; - mb_buffer[1] = '\0'; - break; - - case 't': - mb_buffer[0] = byte = '\t'; - mb_buffer[1] = '\0'; - break; - - case 'n': - mb_buffer[0] = byte = '\n'; - mb_buffer[1] = '\0'; - break; - - case 'v': - mb_buffer[0] = byte = '\v'; - mb_buffer[1] = '\0'; - break; - - case 'f': - mb_buffer[0] = byte = '\f'; - mb_buffer[1] = '\0'; - break; - - case 'r': - mb_buffer[0] = byte = '\r'; - mb_buffer[1] = '\0'; - break; - - case 'u': - mb_buffer[0] = '\\'; - mb_buffer[1] = 'u'; - mb_buffer[2] = '\0'; - break; - - case '\\': - mb_buffer[0] = byte = '\\'; - mb_buffer[1] = '\0'; - break; - } - else - { - /* Manage double quotes */ - /* """""""""""""""""""" */ - if (byte == '"' && !is_squote) - is_dquote = !is_dquote; - - /* Manage single quotes */ - /* """""""""""""""""""" */ - if (byte == '\'' && !is_dquote) - is_squote = !is_squote; - } - - /* Only consider delimiters when outside quotations */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - if ((!is_dquote && !is_squote) - && ll_find(word_delims_list, mb_buffer, delims_cmp) != NULL) - break; - - /* We no dot count the significant quotes */ - /* """""""""""""""""""""""""""""""""""""" */ - if (!is_special - && ((byte == '"' && !is_squote) || (byte == '\'' && !is_dquote))) - { - is_special = 0; - goto next; - } - - /* Feed temp with the content of mb_buffer */ - /* """"""""""""""""""""""""""""""""""""""" */ - while (mb_buffer[i] != '\0') - { - if (mb_count >= wordsize - 1) - temp = xrealloc(temp, - wordsize += (mb_count / CHARSCHUNK + 1) * CHARSCHUNK); - - *(temp + mb_count++) = mb_buffer[i]; - i++; - } - - is_special = 0; - - next: - byte = get_bytes(input, mb_buffer, langinfo); - } - - /* Nul-terminate the word to make it a string */ - /* """""""""""""""""""""""""""""""""""""""""" */ - *(temp + mb_count) = '\0'; - - /* Replace the UTF-8 ASCII representations in the word just */ - /* read by their binary values. */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_interpret(temp, langinfo); - - /* Skip all field delimiters before a record delimiter */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - if (ll_find(record_delims_list, mb_buffer, delims_cmp) == NULL) - { - byte = get_bytes(input, mb_buffer, langinfo); - while (byte != EOF - && ll_find(word_delims_list, mb_buffer, delims_cmp) != NULL - && ll_find(record_delims_list, mb_buffer, delims_cmp) == NULL) - byte = get_bytes(input, mb_buffer, langinfo); - - if (langinfo->utf8 && mb_get_length(mb_buffer[0]) > 1) - { - size_t pos; - - pos = strlen(mb_buffer); - while (pos > 0) - my_ungetc(mb_buffer[--pos]); - } - else - my_ungetc(byte); - } - - /* Mark it as the last word of a record if its sequence matches a */ - /* record delimiter except in tab mode */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (byte == EOF - || ((win->col_mode || win->line_mode) - && ll_find(record_delims_list, mb_buffer, delims_cmp) != NULL)) - *is_last = 1; - else - *is_last = 0; - - /* Remove the ANSI color escape sequences from the word */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ - strip_ansi_color(temp, toggle); - - return temp; -} - -/* ========================================================= */ -/* Set a foreground color according to terminal capabilities */ -/* ========================================================= */ -static void -set_foreground_color(term_t * term, short color) -{ - if (term->color_method == 0 && term->has_setf) - tputs(TPARM2(set_foreground, color), 1, outch); - else if (term->color_method == 1 && term->has_setaf) - tputs(TPARM2(set_a_foreground, color), 1, outch); -} - -/* ========================================================= */ -/* Set a background color according to terminal capabilities */ -/* ========================================================= */ -static void -set_background_color(term_t * term, short color) -{ - if (term->color_method == 0 && term->has_setb) - tputs(TPARM2(set_background, color), 1, outch); - else if (term->color_method == 1 && term->has_setab) - tputs(TPARM2(set_a_background, color), 1, outch); -} - -/* ====================================================== */ -/* Put a scrolling symbol at the first column of the line */ -/* ====================================================== */ -static void -left_margin_putp(char * s, term_t * term, win_t * win) -{ - apply_attr(term, win->shift_attr); - - /* We won't print this symbol when not in column mode */ - /* """""""""""""""""""""""""""""""""""""""""""""""""" */ - if (*s != '\0') - fputs(s, stdout); - - tputs(TPARM1(exit_attribute_mode), 1, outch); -} - -/* ===================================================== */ -/* Put a scrolling symbol at the last column of the line */ -/* ===================================================== */ -static void -right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term, - win_t * win, long line, long offset) -{ - apply_attr(term, win->bar_attr); - - if (term->has_hpa) - tputs(TPARM2(column_address, offset + win->max_width + 1), 1, outch); - else if (term->has_cursor_address) - tputs(TPARM3(cursor_address, term->curs_line + line - 2, - offset + win->max_width + 1), - 1, outch); - else if (term->has_parm_right_cursor) - { - fputc('\r', stdout); - tputs(TPARM2(parm_right_cursor, offset + win->max_width + 1), 1, outch); - } - else - { - long i; - - fputc('\r', stdout); - for (i = 0; i < offset + win->max_width + 1; i++) - tputs(TPARM1(cursor_right), 1, outch); - } - - if (langinfo->utf8) - fputs(s1, stdout); - else - fputs(s2, stdout); - - tputs(TPARM1(exit_attribute_mode), 1, outch); -} - -/* ************** */ -/* Core functions */ -/* ************** */ - -/* ============================================================== */ -/* Split the lines of the message given to -m to a linked list of */ -/* multibytes lines. */ -/* Also fill the maximum screen width and the maximum number */ -/* of bytes of the longest line */ -/* ============================================================== */ -static void -get_message_lines(char * message, ll_t * message_lines_list, - long * message_max_width, long * message_max_len) -{ - char * str; - char * ptr; - char * cr_ptr; - long n; - wchar_t * w = NULL; - - *message_max_width = 0; - *message_max_len = 0; - ptr = message; - - /* For each line terminated with a EOL character */ - /* """"""""""""""""""""""""""""""""""""""""""""" */ - while (*ptr != '\0' && (cr_ptr = strchr(ptr, '\n')) != NULL) - { - if (cr_ptr > ptr) - { - str = xmalloc(cr_ptr - ptr + 1); - str[cr_ptr - ptr] = '\0'; - memcpy(str, ptr, cr_ptr - ptr); - } - else - str = xstrdup(""); - - ll_append(message_lines_list, str); - - /* If needed, update the message maximum width */ - /* """"""""""""""""""""""""""""""""""""""""""" */ - n = wcswidth((w = mb_strtowcs(str)), mb_strlen(str)); - free(w); - - if (n > *message_max_width) - *message_max_width = n; - - /* If needed, update the message maximum number */ - /* of bytes used by the longest line */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - if ((n = (long)strlen(str)) > *message_max_len) - *message_max_len = n; - - ptr = cr_ptr + 1; - } - - /* For the last line */ - /* """"""""""""""""" */ - if (*ptr != '\0') - { - ll_append(message_lines_list, xstrdup(ptr)); - - n = wcswidth((w = mb_strtowcs(ptr)), mb_strlen(ptr)); - free(w); - - if (n > *message_max_width) - *message_max_width = n; - - /* If needed, update the message maximum number */ - /* of bytes used by the longest line */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - if ((n = (long)strlen(ptr)) > *message_max_len) - *message_max_len = n; - } - else - ll_append(message_lines_list, xstrdup("")); -} - -/* =================================================================== */ -/* Set the new start and the new end of the window structure according */ -/* to the current cursor position. */ -/* =================================================================== */ -static void -set_win_start_end(win_t * win, long current, long last) -{ - long cur_line, end_line; - - cur_line = line_nb_of_word_a[current]; - if (cur_line == last) - win->end = count - 1; - else - { - /* In help mode we must not modify the windows start/end position as */ - /* It must be redrawn exactly as it was before. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!help_mode) - { - if (cur_line + win->max_lines / 2 + 1 <= last) - win->end = first_word_in_line_a[cur_line + win->max_lines / 2 + 1] - 1; - else - win->end = first_word_in_line_a[last]; - } - } - end_line = line_nb_of_word_a[win->end]; - - if (end_line < win->max_lines) - win->start = 0; - else - win->start = first_word_in_line_a[end_line - win->max_lines + 1]; -} - -/* ========================================================================= */ -/* Set the metadata associated with a word, its starting and ending position */ -/* the line in which it is put and so on. */ -/* Set win.start win.end and the starting and ending position of each word. */ -/* This function is only called initially, when resizing the terminal and */ -/* potentially when the search function is used. */ -/* ========================================================================= */ -static long -build_metadata(term_t * term, long count, win_t * win) -{ - long i = 0; - long word_len; - long len = 0; - long last = 0; - long word_width; - long tab_count; /* Current number of words in the line, * - * used in tab_mode */ - wchar_t * w; - - line_nb_of_word_a[0] = 0; - first_word_in_line_a[0] = 0; - - /* In column mode we need to calculate win->max_width, first initialize */ - /* it to 0 and increment it later in the loop */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!win->col_mode) - win->max_width = 0; - - tab_count = 0; - while (i < count) - { - /* Determine the number of screen positions used by the word */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - word_len = mbstowcs(NULL, word_a[i].str, 0); - word_width = wcswidth((w = mb_strtowcs(word_a[i].str)), word_len); - - /* Manage the case where the word is larger than the terminal width */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (word_width >= term->ncolumns - 2) - { - /* Shorten the word until it fits */ - /* """""""""""""""""""""""""""""" */ - do - { - word_width = wcswidth(w, word_len--); - } while (word_len > 0 && word_width >= term->ncolumns - 2); - } - free(w); - - /* Look if there is enough remaining place on the line when not in column */ - /* mode. Force a break if the 'is_last' flag is set in all modes or if we */ - /* hit the max number of allowed columns in tab mode */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if ((!win->col_mode && !win->line_mode - && (len + word_width + 1) >= term->ncolumns - 1) - || ((win->col_mode || win->line_mode || win->tab_mode) && i > 0 - && word_a[i - 1].is_last) - || (win->tab_mode && win->max_cols > 0 && tab_count >= win->max_cols)) - { - - /* We must build another line */ - /* """""""""""""""""""""""""" */ - line_nb_of_word_a[i] = ++last; - first_word_in_line_a[last] = i; - - word_a[i].start = 0; - - len = word_width + 1; /* Resets the current line length */ - tab_count = 1; /* Resets the current number of words * - * in the line */ - word_a[i].end = word_width - 1; - word_a[i].mb = word_len + 1; + /* Look if there is enough remaining place on the line when not in column */ + /* mode. Force a break if the 'is_last' flag is set in all modes or if we */ + /* hit the max number of allowed columns in tab mode */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if ((!win->col_mode && !win->line_mode + && (len + word_width + 1) >= term->ncolumns - 1) + || ((win->col_mode || win->line_mode || win->tab_mode) && i > 0 + && word_a[i - 1].is_last) + || (win->tab_mode && win->max_cols > 0 && tab_count >= win->max_cols)) + { + + /* We must build another line */ + /* """""""""""""""""""""""""" */ + line_nb_of_word_a[i] = ++last; + first_word_in_line_a[last] = i; + + word_a[i].start = 0; + + len = word_width + 1; /* Resets the current line length */ + tab_count = 1; /* Resets the current number of words * + * in the line */ + word_a[i].end = word_width - 1; + word_a[i].mb = word_len; } else { word_a[i].start = len; word_a[i].end = word_a[i].start + word_width - 1; - word_a[i].mb = word_len + 1; + word_a[i].mb = word_len; line_nb_of_word_a[i] = last; len += word_width + 1; /* Increase line length */ @@ -5341,7 +2985,87 @@ else apply_attr(term, win->cursor_attr); - for (i = 0; i < word_a[pos].mb - 1 - daccess.flength; i++) + for (i = 0; i < word_a[pos].mb - daccess.flength; i++) + { + if (BIT_ISSET(word_a[pos].bitmap, i)) + { + if (!att_set) + { + att_set = 1; + + /* Set the buffer display attribute */ + /* """""""""""""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + if (err) + apply_attr(term, win->match_err_text_attr); + else + apply_attr(term, win->match_text_attr); + + if (word_a[pos].is_tagged) + apply_attr(term, win->cursor_on_tag_attr); + else + apply_attr(term, win->cursor_attr); + } + } + else + { + if (att_set) + { + att_set = 0; + + /* Set the search cursor attribute */ + /* """"""""""""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + if (word_a[pos].is_tagged) + apply_attr(term, win->cursor_on_tag_attr); + else + apply_attr(term, win->cursor_attr); + } + } + np = utf8_next(p); + if (np == NULL) + fputs(p, stdout); + else + printf("%.*s", (int)(np - p), p); + p = np; + } +} + +/* ==================================================================== */ +/* Helper function used by disp_word to print a matching word NOT under */ +/* the cursor with the matching characters of the word highlighted. */ +/* ==================================================================== */ +void +disp_matching_word(long pos, win_t * win, term_t * term, int is_cursor, int err) +{ + size_t i; + int att_set = 0; + char * p = word_a[pos].str + daccess.flength; + char * np; + + /* Set the search cursor attribute */ + /* """"""""""""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + + if (is_cursor) + { + if (err) + apply_attr(term, win->match_err_field_attr); + else + apply_attr(term, win->match_field_attr); + } + else + { + if (err) + apply_attr(term, win->search_err_field_attr); + else + apply_attr(term, win->search_field_attr); + } + + if (word_a[pos].is_tagged) + apply_attr(term, win->tag_attr); + + for (i = 0; i < word_a[pos].mb - daccess.flength; i++) { if (BIT_ISSET(word_a[pos].bitmap, i)) { @@ -5352,15 +3076,18 @@ /* Set the buffer display attribute */ /* """""""""""""""""""""""""""""""" */ tputs(TPARM1(exit_attribute_mode), 1, outch); - if (err) - apply_attr(term, win->match_err_text_attr); + if (is_cursor) + { + if (err) + apply_attr(term, win->match_err_text_attr); + else + apply_attr(term, win->match_text_attr); + } else - apply_attr(term, win->match_text_attr); + apply_attr(term, win->search_text_attr); if (word_a[pos].is_tagged) - apply_attr(term, win->cursor_on_tag_attr); - else - apply_attr(term, win->cursor_attr); + apply_attr(term, win->tag_attr); } } else @@ -5372,13 +3099,27 @@ /* Set the search cursor attribute */ /* """"""""""""""""""""""""""""""" */ tputs(TPARM1(exit_attribute_mode), 1, outch); - if (word_a[pos].is_tagged) - apply_attr(term, win->cursor_on_tag_attr); + if (is_cursor) + { + if (err) + apply_attr(term, win->match_err_field_attr); + else + apply_attr(term, win->match_field_attr); + } else - apply_attr(term, win->cursor_attr); + { + if (err) + apply_attr(term, win->search_err_field_attr); + else + apply_attr(term, win->search_field_attr); + } + + if (word_a[pos].is_tagged) + apply_attr(term, win->tag_attr); } } - np = mb_next(p); + + np = utf8_next(p); if (np == NULL) fputs(p, stdout); else @@ -5387,1171 +3128,1535 @@ } } -/* ==================================================================== */ -/* Helper function used by disp_word to print a matching word NOT under */ -/* the cursor withe the matching characters of the word highlighted. */ -/* ==================================================================== */ +/* ====================================================================== */ +/* Display a word in, the windows. Manages the following different cases: */ +/* - Search mode display */ +/* - Cursor display */ +/* - Normal display */ +/* - Color or mono display */ +/* ====================================================================== */ void -disp_matching_word(long pos, win_t * win, term_t * term, int is_cursor, int err) +disp_word(long pos, search_mode_t search_mode, search_data_t * search_data, + term_t * term, win_t * win, char * tmp_word) { - size_t i; - int att_set = 0; - char * p = word_a[pos].str + daccess.flength; - char * np; + long s = word_a[pos].start; + long e = word_a[pos].end; + long p; + + char * buffer = search_data->buf; + + if (pos == current) + { + if (search_mode != NONE) + { + utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p); + if (word_a[pos].is_numbered) + { + /* Set the direct access number attribute */ + /* """""""""""""""""""""""""""""""""""""" */ + apply_attr(term, win->daccess_attr); + + /* And print it */ + /* """""""""""" */ + fputs(daccess.left, stdout); + printf("%.*s", daccess.length, tmp_word + 1); + fputs(daccess.right, stdout); + tputs(TPARM1(exit_attribute_mode), 1, outch); + fputc(' ', stdout); + } + else if (daccess.length > 0) + { + /* prints the leading spaces */ + /* """"""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + printf("%.*s", daccess.flength, tmp_word); + } + + /* Set the search cursor attribute */ + /* """"""""""""""""""""""""""""""" */ + if (search_data->fuzzy_err) + apply_attr(term, win->search_err_field_attr); + else + apply_attr(term, win->search_field_attr); + + /* The tab attribute must complete the attributes already set */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (word_a[pos].is_tagged) + apply_attr(term, win->tag_attr); + + /* Print the word part */ + /* """"""""""""""""""" */ + fputs(tmp_word + daccess.flength, stdout); + + if (buffer[0] != '\0') + { + long i = 0; + + /* Put the cursor at the beginning of the word */ + /* """"""""""""""""""""""""""""""""""""""""""" */ + for (i = 0; i < e - s + 1 - daccess.flength; i++) + tputs(TPARM1(cursor_left), 1, outch); + + tputs(TPARM1(exit_attribute_mode), 1, outch); + + /* Set the search cursor attribute */ + /* """"""""""""""""""""""""""""""" */ + if (search_data->fuzzy_err) + apply_attr(term, win->search_err_field_attr); + else + apply_attr(term, win->search_field_attr); + + disp_matching_word(pos, win, term, 0, search_data->fuzzy_err); + } + } + else + { + if (daccess.length > 0) + { + /* If this word is not numbered, reset the display */ + /* attributes before printing the leading spaces. */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + if (!word_a[pos].is_numbered) + { + /* Print the non significant part of the word */ + /* """""""""""""""""""""""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + printf("%.*s", daccess.flength - 1, word_a[pos].str); + tputs(TPARM1(exit_attribute_mode), 1, outch); + fputc(' ', stdout); + } + else + { + apply_attr(term, win->daccess_attr); + + /* Print the non significant part of the word */ + /* """""""""""""""""""""""""""""""""""""""""" */ + fputs(daccess.left, stdout); + printf("%.*s", daccess.length, word_a[pos].str + 1); + fputs(daccess.right, stdout); + tputs(TPARM1(exit_attribute_mode), 1, outch); + fputc(' ', stdout); + } + } + + /* If we are not in search mode, display a normal cursor */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p); + if (word_a[pos].is_matching) + disp_cursor_word(pos, win, term, search_data->fuzzy_err); + else + { + if (word_a[pos].is_tagged) + apply_attr(term, win->cursor_on_tag_attr); + else + apply_attr(term, win->cursor_attr); + + fputs(tmp_word + daccess.flength, stdout); + } + } + tputs(TPARM1(exit_attribute_mode), 1, outch); + } + else + { + /* Display a normal word without any attribute */ + /* """"""""""""""""""""""""""""""""""""""""""" */ + utf8_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb, &p); + + /* If words are numbered, emphasis their numbers */ + /* """"""""""""""""""""""""""""""""""""""""""""" */ + if (word_a[pos].is_numbered) + { + apply_attr(term, win->daccess_attr); + + fputs(daccess.left, stdout); + printf("%.*s", daccess.length, tmp_word + 1); + fputs(daccess.right, stdout); + + tputs(TPARM1(exit_attribute_mode), 1, outch); + fputc(' ', stdout); + } + else if (daccess.length > 0) + { + long i; + + /* Insert leading spaces if the word is non numbered and */ + /* padding for all words is set */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + if (daccess.padding == 'a') + for (i = 0; i < daccess.flength; i++) + fputc(' ', stdout); + } + + if (!word_a[pos].is_selectable) + apply_attr(term, win->exclude_attr); + else if (word_a[pos].special_level > 0) + { + long level = word_a[pos].special_level - 1; + + apply_attr(term, win->special_attr[level]); + } + else + apply_attr(term, win->include_attr); + + if (word_a[pos].is_matching) + disp_matching_word(pos, win, term, 1, search_data->fuzzy_err); + else + { + if (word_a[pos].is_tagged) + apply_attr(term, win->tag_attr); + + if ((daccess.length > 0 && daccess.padding == 'a') + || word_a[pos].is_numbered) + fputs(tmp_word + daccess.flength, stdout); + else + fputs(tmp_word, stdout); + } + + tputs(TPARM1(exit_attribute_mode), 1, outch); + } +} + +/* ======================================= */ +/* Display a message line above the window */ +/* ======================================= */ +void +disp_message(ll_t * message_lines_list, long message_max_width, + long message_max_len, term_t * term, win_t * win) +{ + ll_node_t * node; + char * line; + char * buf; + size_t len; + long size; + long offset; + wchar_t * w; + + win->message_lines = 0; + + /* Disarm the periodic timer to prevent the interruptions to corrupt */ + /* screen by altering the timing of the decoding of the terminfo */ + /* capabilities de */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + periodic_itv.it_value.tv_sec = 0; + periodic_itv.it_value.tv_usec = 0; + periodic_itv.it_interval.tv_sec = 0; + periodic_itv.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &periodic_itv, NULL); + + /* Do nothing if there is no message to display */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + if (message_lines_list == NULL) + return; - /* Set the search cursor attribute */ - /* """"""""""""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); + node = message_lines_list->head; + buf = xmalloc(message_max_len + 1); - if (is_cursor) - { - if (err) - apply_attr(term, win->match_err_field_attr); - else - apply_attr(term, win->match_field_attr); - } - else + /* Follow the message lines list and display each line */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + while (node != NULL) { - if (err) - apply_attr(term, win->search_err_field_attr); - else - apply_attr(term, win->search_field_attr); - } + long i; - if (word_a[pos].is_tagged) - apply_attr(term, win->tag_attr); + line = node->data; + len = utf8_strlen(line); + w = utf8_strtowcs(line); - for (i = 0; i < word_a[pos].mb - 1 - daccess.flength; i++) - { - if (BIT_ISSET(word_a[pos].bitmap, i)) - { - if (!att_set) - { - att_set = 1; + /* Adjust size and len if the terminal is not large enough */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ + size = wcswidth(w, len); + while (len > 0 && size > term->ncolumns) + size = wcswidth(w, --len); - /* Set the buffer display attribute */ - /* """""""""""""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); - if (is_cursor) - { - if (err) - apply_attr(term, win->match_err_text_attr); - else - apply_attr(term, win->match_text_attr); - } - else - apply_attr(term, win->search_text_attr); + free(w); - if (word_a[pos].is_tagged) - apply_attr(term, win->tag_attr); - } - } - else - { - if (att_set) - { - att_set = 0; + /* Compute the offset from the left screen border if -M option is set */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + offset = (term->ncolumns - message_max_width - 3) / 2; - /* Set the search cursor attribute */ - /* """"""""""""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); - if (is_cursor) - { - if (err) - apply_attr(term, win->match_err_field_attr); - else - apply_attr(term, win->match_field_attr); - } - else - { - if (err) - apply_attr(term, win->search_err_field_attr); - else - apply_attr(term, win->search_field_attr); - } + if (win->center && offset > 0) + for (i = 0; i < offset; i++) + fputc(' ', stdout); - if (word_a[pos].is_tagged) - apply_attr(term, win->tag_attr); - } + apply_attr(term, win->message_attr); + + /* Only print the start of a line if the screen width if too small */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + utf8_strprefix(buf, line, len, &size); + + /* Print the line without the ending \n */ + /* '''''''''''''''''''''''''''''''''''' */ + printf("%s", buf); + + /* Complete the short line with spaces until it reach the */ + /* message max size */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + for (i = size; i < message_max_width; i++) + { + if (i + (offset < 0 ? 0 : offset) >= term->ncolumns) + break; + fputc(' ', stdout); } - np = mb_next(p); - if (np == NULL) - fputs(p, stdout); - else - printf("%.*s", (int)(np - p), p); - p = np; + /* Drop the attributes and print a \n */ + /* '''''''''''''''''''''''''''''''''' */ + tputs(TPARM1(exit_attribute_mode), 1, outch); + puts(""); + + node = node->next; + win->message_lines++; } + + /* Add an empty line without attribute to separate the menu title */ + /* and the menu content. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + puts(""); + win->message_lines++; + + free(buf); + + /* Re-arm the periodic timer */ + /* """"""""""""""""""""""""" */ + periodic_itv.it_value.tv_sec = 0; + periodic_itv.it_value.tv_usec = TICK; + periodic_itv.it_interval.tv_sec = 0; + periodic_itv.it_interval.tv_usec = TICK; + setitimer(ITIMER_REAL, &periodic_itv, NULL); } -/* ====================================================================== */ -/* Display a word in, the windows. Manages the following different cases: */ -/* - Search mode display */ -/* - Cursor display */ -/* - Normal display */ -/* - Color or mono display */ -/* ====================================================================== */ -static void -disp_word(long pos, search_mode_t search_mode, search_data_t * search_data, - term_t * term, win_t * win, char * tmp_word) +/* ============================ */ +/* Display the selection window */ +/* ============================ */ +long +disp_lines(win_t * win, toggle_t * toggle, long current, long count, + search_mode_t search_mode, search_data_t * search_data, + term_t * term, long last_line, char * tmp_word, + langinfo_t * langinfo) { - long s = word_a[pos].start; - long e = word_a[pos].end; - long p; + long lines_disp; + long i; + char scroll_symbol[5]; + long len; + long display_bar; - char * buffer = search_data->buf; + /* Disarm the periodic timer to prevent the interruptions to corrupt */ + /* screen by altering the timing of the decoding of the terminfo */ + /* capabilities de */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + periodic_itv.it_value.tv_sec = 0; + periodic_itv.it_value.tv_usec = 0; + periodic_itv.it_interval.tv_sec = 0; + periodic_itv.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &periodic_itv, NULL); - if (pos == current) - { - if (search_mode != NONE) - { - mb_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb - 1, &p); - if (word_a[pos].is_numbered) - { - /* Set the direct access number attribute */ - /* """""""""""""""""""""""""""""""""""""" */ - apply_attr(term, win->daccess_attr); + scroll_symbol[0] = ' '; + scroll_symbol[1] = '\0'; - /* And print it */ - /* """""""""""" */ - fputs(daccess.left, stdout); - printf("%.*s", daccess.length, tmp_word + 1); - fputs(daccess.right, stdout); - tputs(TPARM1(exit_attribute_mode), 1, outch); - fputc(' ', stdout); - } - else if (daccess.length > 0) - { - /* prints the leading spaces */ - /* """"""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); - printf("%.*s", daccess.flength, tmp_word); - } + lines_disp = 1; - /* Set the search cursor attribute */ - /* """"""""""""""""""""""""""""""" */ - if (search_data->fuzzy_err) - apply_attr(term, win->search_err_field_attr); - else - apply_attr(term, win->search_field_attr); + tputs(TPARM1(save_cursor), 1, outch); - /* The tab attribute must complete the attributes already set */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (word_a[pos].is_tagged) - apply_attr(term, win->tag_attr); + i = win->start; - /* Print the word part */ - /* """"""""""""""""""" */ - fputs(tmp_word + daccess.flength, stdout); + /* Modify the max number of displayed lines if we do not have */ + /* enough place */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (win->max_lines > term->nlines - win->message_lines) + win->max_lines = term->nlines - win->message_lines; - if (buffer[0] != '\0') - { - long i = 0; + if (last_line >= win->max_lines) + display_bar = 1; + else + display_bar = 0; - /* Put the cursor at the beginning of the word */ - /* """"""""""""""""""""""""""""""""""""""""""" */ - for (i = 0; i < e - s + 1 - daccess.flength; i++) - tputs(TPARM1(cursor_left), 1, outch); + if (win->col_mode || win->line_mode) + len = term->ncolumns - 3; + else + len = term->ncolumns - 2; - tputs(TPARM1(exit_attribute_mode), 1, outch); + /* If in column mode and the sum of the columns sizes + gutters is */ + /* greater than the terminal width, then prepend a space to be able to */ + /* display the left arrow indicating that the first displayed column */ + /* is not the first one. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (len > 1 + && ((win->col_mode || win->line_mode) + && win->real_max_width > term->ncolumns - 2)) + { + if (win->first_column > 0) + { + if (langinfo->utf8) + strcpy(scroll_symbol, shift_left_sym); + else + strcpy(scroll_symbol, "<"); + } + } + else + scroll_symbol[0] = '\0'; - /* Set the search cursor attribute */ - /* """"""""""""""""""""""""""""""" */ - if (search_data->fuzzy_err) - apply_attr(term, win->search_err_field_attr); - else - apply_attr(term, win->search_field_attr); + /* Center the display ? */ + /* """""""""""""""""""" */ + if (win->offset > 0) + { + long i; + for (i = 0; i < win->offset; i++) + fputc(' ', stdout); + } - disp_matching_word(pos, win, term, 0, search_data->fuzzy_err); - } - } - else + left_margin_putp(scroll_symbol, term, win); + while (len > 1 && i <= count - 1) + { + /* Display one word and the space or symbol following it */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (word_a[i].start >= win->first_column + && word_a[i].end < len + win->first_column) { - if (daccess.length > 0) + disp_word(i, search_mode, search_data, term, win, tmp_word); + + /* If there are more element to be displayed after the right margin */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if ((win->col_mode || win->line_mode) && i < count - 1 + && word_a[i + 1].end >= len + win->first_column) { - /* If this word is not numbered, reset the display */ - /* attributes before printing the leading spaces. */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (!word_a[pos].is_numbered) - { - /* Print the non significant part of the word */ - /* """""""""""""""""""""""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); - printf("%.*s", daccess.flength - 1, word_a[pos].str); - tputs(TPARM1(exit_attribute_mode), 1, outch); - fputc(' ', stdout); - } + apply_attr(term, win->shift_attr); + + if (langinfo->utf8) + fputs(shift_right_sym, stdout); else - { - apply_attr(term, win->daccess_attr); + fputc('>', stdout); - /* Print the non significant part of the word */ - /* """""""""""""""""""""""""""""""""""""""""" */ - fputs(daccess.left, stdout); - printf("%.*s", daccess.length, word_a[pos].str + 1); - fputs(daccess.right, stdout); - tputs(TPARM1(exit_attribute_mode), 1, outch); - fputc(' ', stdout); - } + tputs(TPARM1(exit_attribute_mode), 1, outch); } - /* If we are not in search mode, display a normal cursor */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb - 1, &p); - if (word_a[pos].is_matching) - disp_cursor_word(pos, win, term, search_data->fuzzy_err); - else + /* If we want to display the gutter */ + /* """""""""""""""""""""""""""""""" */ + else if (!word_a[i].is_last && win->col_sep + && (win->tab_mode || win->col_mode)) { - if (word_a[pos].is_tagged) - apply_attr(term, win->cursor_on_tag_attr); - else - apply_attr(term, win->cursor_attr); + long pos; - fputs(tmp_word + daccess.flength, stdout); + /* Make sure that we are using the right gutter character even */ + /* if the first displayed word is * not the first of its line */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + pos = i - first_word_in_line_a[line_nb_of_word_a[i]]; + + if (pos >= win->gutter_nb) /* Use the last gutter character */ + fputs(win->gutter_a[win->gutter_nb - 1], stdout); + else + fputs(win->gutter_a[pos], stdout); } + else + /* Else just display a space */ + /* """"""""""""""""""""""""" */ + fputc(' ', stdout); } - tputs(TPARM1(exit_attribute_mode), 1, outch); - } - else - { - /* Display a normal word without any attribute */ - /* """"""""""""""""""""""""""""""""""""""""""" */ - mb_strprefix(tmp_word, word_a[pos].str, (long)word_a[pos].mb - 1, &p); - - /* If words are numbered, emphasis their numbers */ - /* """"""""""""""""""""""""""""""""""""""""""""" */ - if (word_a[pos].is_numbered) - { - apply_attr(term, win->daccess_attr); - fputs(daccess.left, stdout); - printf("%.*s", daccess.length, tmp_word + 1); - fputs(daccess.right, stdout); + /* Mark the line as the current line, the line containing the cursor */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (i == current) + win->cur_line = lines_disp; - tputs(TPARM1(exit_attribute_mode), 1, outch); - fputc(' ', stdout); - } - else if (daccess.length > 0) + /* Check if we must start a new line */ + /* """"""""""""""""""""""""""""""""" */ + if (i == count - 1 || word_a[i + 1].start == 0) { - long i; + tputs(TPARM1(clr_eol), 1, outch); + if (lines_disp < win->max_lines) + { + /* If we have more than one line to display */ + /* """""""""""""""""""""""""""""""""""""""" */ + if (display_bar && !toggle->no_scrollbar + && (lines_disp > 1 || i < count - 1)) + { + /* Display the next element of the scrollbar */ + /* """"""""""""""""""""""""""""""""""""""""" */ + if (line_nb_of_word_a[i] == 0) + { + if (win->max_lines > 1) + right_margin_putp(sbar_top, "\\", langinfo, term, win, lines_disp, + win->offset); + else + right_margin_putp(sbar_arr_down, "^", langinfo, term, win, + lines_disp, win->offset); + } + else if (lines_disp == 1) + right_margin_putp(sbar_arr_up, "^", langinfo, term, win, lines_disp, + win->offset); + else if (line_nb_of_word_a[i] == last_line) + { + if (win->max_lines > 1) + right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp, + win->offset); + else + right_margin_putp(sbar_arr_up, "^", langinfo, term, win, + lines_disp, win->offset); + } + else if (last_line + 1 > win->max_lines + && (long)((float)(line_nb_of_word_a[current]) + / (last_line + 1) * (win->max_lines - 2) + + 2) + == lines_disp) + right_margin_putp(sbar_curs, "+", langinfo, term, win, lines_disp, + win->offset); + else + right_margin_putp(sbar_line, "|", langinfo, term, win, lines_disp, + win->offset); + } - /* Insert leading spaces if the word is non numbered and */ - /* padding for all words is set */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - tputs(TPARM1(exit_attribute_mode), 1, outch); - if (daccess.padding == 'a') - for (i = 0; i < daccess.flength; i++) - fputc(' ', stdout); - } + /* Print a newline character if we are not at the end of */ + /* the input nor at the end of the window */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (i < count - 1 && lines_disp < win->max_lines) + { + fputc('\n', stdout); - if (!word_a[pos].is_selectable) - apply_attr(term, win->exclude_attr); - else if (word_a[pos].special_level > 0) - { - long level = word_a[pos].special_level - 1; + if (win->offset > 0) + { + long i; + for (i = 0; i < win->offset; i++) + fputc(' ', stdout); + } - apply_attr(term, win->special_attr[level]); - } - else - apply_attr(term, win->include_attr); + left_margin_putp(scroll_symbol, term, win); + } - if (word_a[pos].is_matching) - disp_matching_word(pos, win, term, 1, search_data->fuzzy_err); - else - { - if (word_a[pos].is_tagged) - apply_attr(term, win->tag_attr); + /* We do not increment the number of lines seen after */ + /* a premature end of input */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + if (i < count - 1) + lines_disp++; - if ((daccess.length > 0 && daccess.padding == 'a') - || word_a[pos].is_numbered) - fputs(tmp_word + daccess.flength, stdout); + if (win->max_lines == 1) + break; + } + else if (i <= count - 1 && lines_disp == win->max_lines) + { + /* The last line of the window has been displayed */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + if (display_bar && line_nb_of_word_a[i] == last_line) + { + if (!toggle->no_scrollbar) + { + if (win->max_lines > 1) + right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp, + win->offset); + else + right_margin_putp(sbar_arr_up, "^", langinfo, term, win, + lines_disp, win->offset); + } + } + else + { + if (display_bar && !toggle->no_scrollbar) + right_margin_putp(sbar_arr_down, "v", langinfo, term, win, + lines_disp, win->offset); + break; + } + } else - fputs(tmp_word, stdout); + /* These lines were not in the widows and so we have nothing to do */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + break; } - tputs(TPARM1(exit_attribute_mode), 1, outch); + /* Next word */ + /* """"""""" */ + i++; } -} - -/* ======================================= */ -/* Display a message line above the window */ -/* ======================================= */ -static void -disp_message(ll_t * message_lines_list, long message_max_width, - long message_max_len, term_t * term, win_t * win) -{ - ll_node_t * node; - char * line; - char * buf; - size_t len; - long size; - long offset; - wchar_t * w; - win->message_lines = 0; + /* Update win->end, this is necessary because we only */ + /* call build_metadata on start and on terminal resize */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + if (i == count) + win->end = i - 1; + else + win->end = i; + /* We restore the cursor position saved before the display of the window */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + tputs(TPARM1(restore_cursor), 1, outch); - /* Disarm the periodic timer to prevent the interruptions to corrupt */ - /* screen by altering the timing of the decoding of the terminfo */ - /* capabilities de */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + /* Re-arm the periodic timer */ + /* """"""""""""""""""""""""" */ periodic_itv.it_value.tv_sec = 0; - periodic_itv.it_value.tv_usec = 0; + periodic_itv.it_value.tv_usec = TICK; periodic_itv.it_interval.tv_sec = 0; - periodic_itv.it_interval.tv_usec = 0; + periodic_itv.it_interval.tv_usec = TICK; setitimer(ITIMER_REAL, &periodic_itv, NULL); - /* Do nothing if there is no message to display */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - if (message_lines_list == NULL) - return; + return lines_disp; +} + +/* ============================================ */ +/* Signal handler. Manages SIGWINCH and SIGALRM */ +/* ============================================ */ +void +sig_handler(int s) +{ + switch (s) + { + /* Standard termination signals */ + /* """""""""""""""""""""""""""" */ + case SIGSEGV: + got_sigsegv = 1; + break; + + case SIGTERM: + got_sigterm = 1; + break; - node = message_lines_list->head; - buf = xmalloc(message_max_len + 1); + case SIGHUP: + got_sighup = 1; + break; - if (term->has_bold) - tputs(TPARM1(enter_bold_mode), 1, outch); + /* Terminal resize */ + /* """"""""""""""" */ + case SIGWINCH: + got_winch = 1; + break; - /* Follow the message lines list and display each line */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - while (node != NULL) - { - line = node->data; - len = mb_strlen(line); - w = mb_strtowcs(line); + /* Alarm triggered, This signal is used by the search mechanism to */ + /* forces a window refresh. */ + /* The help mechanism uses it to clear the message */ + /* It is also used to redisplay the window after the end of a terminal */ + /* resizing. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + case SIGALRM: + if (timeout.initial_value > 0) + got_timeout_tick = 1; - size = wcswidth(w, len); - while (len > 0 && size > term->ncolumns) - size = wcswidth(w, --len); + if (help_timer > 0) + help_timer--; - free(w); + if (daccess_timer > 0) + daccess_timer--; - /* Compute the offset from the left screen border if -M option is set */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - offset = (term->ncolumns - message_max_width - 3) / 2; + if (help_timer == 0 && help_mode) + got_help_alrm = 1; - if (win->center && offset > 0) - { - long i; + if (daccess_timer == 0) + got_daccess_alrm = 1; - for (i = 0; i < offset; i++) - putc(' ', stdout); - } + if (winch_timer > 0) + winch_timer--; - /* Only print the start of a line if the screen width if too small */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - mb_strprefix(buf, line, len, &size); - puts(buf); + if (winch_timer == 0) + { + got_winch = 0; + got_help_alrm = 0; + got_winch_alrm = 1; + } - node = node->next; - win->message_lines++; + break; } +} - tputs(TPARM1(exit_attribute_mode), 1, outch); +/* =========================================================== */ +/* Helper function to compare to delimiters for use by ll_find */ +/* =========================================================== */ +int +delims_cmp(const void * a, const void * b) +{ + return strcmp((const char *)a, (const char *)b); +} - free(buf); +/* ========================================================= */ +/* Set new first column to display when horizontal scrolling */ +/* Alter win->first_column */ +/* ========================================================= */ +void +set_new_first_column(win_t * win, term_t * term) +{ + long pos; - /* Re-arm the periodic timer */ - /* """"""""""""""""""""""""" */ - periodic_itv.it_value.tv_sec = 0; - periodic_itv.it_value.tv_usec = TICK; - periodic_itv.it_interval.tv_sec = 0; - periodic_itv.it_interval.tv_usec = TICK; - setitimer(ITIMER_REAL, &periodic_itv, NULL); + if (word_a[current].start < win->first_column) + { + pos = current; + + while (win->first_column > 0 && word_a[current].start < win->first_column) + { + win->first_column = word_a[pos].start; + pos--; + } + } + else if (word_a[current].end - win->first_column >= term->ncolumns - 3) + { + pos = first_word_in_line_a[line_nb_of_word_a[current]]; + + while (!word_a[pos].is_last + && word_a[current].end - win->first_column >= term->ncolumns - 3) + { + pos++; + win->first_column = word_a[pos].start; + } + } } -/* ============================ */ -/* Display the selection window */ -/* ============================ */ -static long -disp_lines(win_t * win, toggle_t * toggle, long current, long count, - search_mode_t search_mode, search_data_t * search_data, - term_t * term, long last_line, char * tmp_word, - langinfo_t * langinfo) +/* ==================================================== */ +/* Restrict the matches to word ending with the pattern */ +/* ==================================================== */ +void +select_ending_matches(win_t * win, term_t * term, search_data_t * search_data, + long * last_line) { - long lines_disp; - long i; - char scroll_symbol[5]; - long len; - long display_bar; + if (matches_count > 0) + { + long i; + long j = 0; + long index; + long nb; + long * tmp; + char * ptr; + char * last_glyph; + int utf8_len; - /* Disarm the periodic timer to prevent the interruptions to corrupt */ - /* screen by altering the timing of the decoding of the terminfo */ - /* capabilities de */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - periodic_itv.it_value.tv_sec = 0; - periodic_itv.it_value.tv_usec = 0; - periodic_itv.it_interval.tv_sec = 0; - periodic_itv.it_interval.tv_usec = 0; - setitimer(ITIMER_REAL, &periodic_itv, NULL); + /* Creation of an alternate array which will */ + /* contain only the candidates having potentially */ + /* an ending pattern, if this array becomes non */ + /* empty then it will replace the original array. */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + alt_matching_words_a = xrealloc(alt_matching_words_a, + matches_count * (sizeof(long))); - scroll_symbol[0] = ' '; - scroll_symbol[1] = '\0'; + for (i = 0; i < matches_count; i++) + { + index = matching_words_a[i]; + long len = word_a[index].len; + char * str = word_a[index].str; - lines_disp = 1; + /* count the trailing blanks non counted in the bitmap */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + ptr = str + strlen(str); - tputs(TPARM1(save_cursor), 1, outch); + nb = 0; + while ((ptr = utf8_prev(str, ptr)) != NULL && isblank(*ptr)) + if (ptr - str + 1 > len) + nb++; + else + break; - i = win->start; + /* Check the bit corresponding to the last non blank glyph */ + /* If set we add the index to an alternate array, if not we */ + /* clear the bitmap of the corresponding word */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (BIT_ISSET(word_a[index].bitmap, + word_a[index].mb - nb - daccess.flength - 1)) + alt_matching_words_a[j++] = index; + else + { + /* Look if the end of the word potentially contain an */ + /* ending pattern. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + if (search_mode == FUZZY) + { + utf8_len = mblen(ptr, 4); + last_glyph = search_data->buf + search_data->len - utf8_len; - /* Modify the max number of displayed lines if we do not have */ - /* enough place */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (win->max_lines > term->nlines - win->message_lines) - win->max_lines = term->nlines - win->message_lines; + /* in fuzzy search mode we only look the last glyph */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + if (memcmp(ptr, last_glyph, utf8_len) == 0) + alt_matching_words_a[j++] = index; + else + memset(word_a[index].bitmap, '\0', + (word_a[index].mb - daccess.flength) / CHAR_BIT + 1); + } + else + { + /* in not fuzzy search mode use all the pattern */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + for (nb = 0; nb < search_data->utf8_len - 1; nb++) + ptr = utf8_prev(str, ptr); + if (memcmp(ptr, search_data->buf, search_data->len) == 0) + alt_matching_words_a[j++] = index; + else + memset(word_a[index].bitmap, '\0', + (word_a[index].mb - daccess.flength) / CHAR_BIT + 1); + } + } + } - if (last_line >= win->max_lines) - display_bar = 1; - else - display_bar = 0; + /* Swap the normal and alt array */ + /* """"""""""""""""""""""""""""" */ + matches_count = j; + matching_words_a_size = j; - if (win->col_mode || win->line_mode) - len = term->ncolumns - 3; - else - len = term->ncolumns - 2; + tmp = matching_words_a; + matching_words_a = alt_matching_words_a; + alt_matching_words_a = tmp; - /* If in column mode and the sum of the columns sizes + gutters is */ - /* greater than the terminal width, then prepend a space to be able to */ - /* display the left arrow indicating that the first displayed column */ - /* is not the first one. */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (len > 1 - && ((win->col_mode || win->line_mode) - && win->real_max_width > term->ncolumns - 2)) - { - if (win->first_column > 0) + if (j > 0) { - if (langinfo->utf8) - strcpy(scroll_symbol, shift_left_sym); - else - strcpy(scroll_symbol, "<"); + /* Adjust the bitmap to the ending version */ + /* """"""""""""""""""""""""""""""""""""""" */ + update_bitmaps(search_mode, search_data, END_AFFINITY); + + current = matching_words_a[0]; + + if (current < win->start || current > win->end) + *last_line = build_metadata(term, count, win); + + /* Set new first column to display */ + /* """"""""""""""""""""""""""""""" */ + set_new_first_column(win, term); } } - else - scroll_symbol[0] = '\0'; +} - /* Center the display ? */ - /* """""""""""""""""""" */ - if (win->offset > 0) +/* ====================================================== */ +/* Restrict the matches to word starting with the pattern */ +/* ====================================================== */ +void +select_starting_matches(win_t * win, term_t * term, search_data_t * search_data, + long * last_line) +{ + if (matches_count > 0) { - long i; - for (i = 0; i < win->offset; i++) - fputc(' ', stdout); - } + long i; + long j = 0; + long index; + long * tmp; + long pos; + char * first_glyph; + int utf8_len; - left_margin_putp(scroll_symbol, term, win); - while (len > 1 && i <= count - 1) - { - /* Display one word and the space or symbol following it */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (word_a[i].start >= win->first_column - && word_a[i].end < len + win->first_column) - { - disp_word(i, search_mode, search_data, term, win, tmp_word); + alt_matching_words_a = xrealloc(alt_matching_words_a, + matches_count * (sizeof(long))); - /* If there are more element to be displayed after the right margin */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if ((win->col_mode || win->line_mode) && i < count - 1 - && word_a[i + 1].end >= len + win->first_column) + first_glyph = xmalloc(5); + + for (i = 0; i < matches_count; i++) + { + index = matching_words_a[i]; + if (BIT_ISSET(word_a[index].bitmap, 0)) + alt_matching_words_a[j++] = index; + else { - apply_attr(term, win->shift_attr); + if (search_mode == FUZZY) + { + first_glyph = utf8_strprefix(first_glyph, + word_a[index].str + daccess.flength, 1, + &pos); + utf8_len = pos; - if (langinfo->utf8) - fputs(shift_right_sym, stdout); + /* in fuzzy search mode we only look the first glyph */ + /* """"""""""""""""""""""""""""""""""""""""""""""""" */ + if (memcmp(search_data->buf, first_glyph, utf8_len) == 0) + alt_matching_words_a[j++] = index; + else + memset(word_a[index].bitmap, '\0', + (word_a[index].mb - daccess.flength) / CHAR_BIT + 1); + } else - fputc('>', stdout); - - tputs(TPARM1(exit_attribute_mode), 1, outch); + { + /* in not fuzzy search mode use all the pattern */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + if (memcmp(search_data->buf, word_a[index].str, search_data->len) + == 0) + alt_matching_words_a[j++] = index; + else + memset(word_a[index].bitmap, '\0', + (word_a[index].mb - daccess.flength) / CHAR_BIT + 1); + } } + } - /* If we want to display the gutter */ - /* """""""""""""""""""""""""""""""" */ - else if (!word_a[i].is_last && win->col_sep - && (win->tab_mode || win->col_mode)) - { - long pos; + free(first_glyph); - /* Make sure that we are using the right gutter character even */ - /* if the first displayed word is * not the first of its line */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - pos = i - first_word_in_line_a[line_nb_of_word_a[i]]; + matches_count = j; + matching_words_a_size = j; - if (pos >= win->gutter_nb) /* Use the last gutter character */ - fputs(win->gutter_a[win->gutter_nb - 1], stdout); - else - fputs(win->gutter_a[pos], stdout); - } - else - /* Else just display a space */ - /* """"""""""""""""""""""""" */ - fputc(' ', stdout); + tmp = matching_words_a; + matching_words_a = alt_matching_words_a; + alt_matching_words_a = tmp; + + if (j > 0) + { + /* Adjust the bitmap to the ending version */ + /* """"""""""""""""""""""""""""""""""""""" */ + update_bitmaps(search_mode, search_data, START_AFFINITY); + + current = matching_words_a[0]; + + if (current < win->start || current > win->end) + *last_line = build_metadata(term, count, win); + + /* Set new first column to display */ + /* """"""""""""""""""""""""""""""" */ + set_new_first_column(win, term); } + } +} - /* Mark the line as the current line, the line containing the cursor */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (i == current) - win->cur_line = lines_disp; +/* ====================== */ +/* Moves the cursor left */ +/* ====================== */ +void +move_left(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long last_line, char * tmp_word) +{ + long old_current = current; + long old_start = win->start; + long old_first_column = win->first_column; + long wi; /* word index */ - /* Check if we must start a new line */ - /* """"""""""""""""""""""""""""""""" */ - if (i == count - 1 || word_a[i + 1].start == 0) + do + { + if (current > 0) { - tputs(TPARM1(clr_eol), 1, outch); - if (lines_disp < win->max_lines) - { - /* If we have more than one line to display */ - /* """""""""""""""""""""""""""""""""""""""" */ - if (display_bar && !toggle->no_scrollbar - && (lines_disp > 1 || i < count - 1)) + if (current == win->start) + if (win->start > 0) { - /* Display the next element of the scrollbar */ - /* """"""""""""""""""""""""""""""""""""""""" */ - if (line_nb_of_word_a[i] == 0) - { - if (win->max_lines > 1) - right_margin_putp(sbar_top, "\\", langinfo, term, win, lines_disp, - win->offset); - else - right_margin_putp(sbar_arr_down, "^", langinfo, term, win, - lines_disp, win->offset); - } - else if (lines_disp == 1) - right_margin_putp(sbar_arr_up, "^", langinfo, term, win, lines_disp, - win->offset); - else if (line_nb_of_word_a[i] == last_line) + for (wi = win->start - 1; wi >= 0 && word_a[wi].start != 0; wi--) { - if (win->max_lines > 1) - right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp, - win->offset); - else - right_margin_putp(sbar_arr_up, "^", langinfo, term, win, - lines_disp, win->offset); } - else if (last_line + 1 > win->max_lines - && (long)((float)(line_nb_of_word_a[current]) - / (last_line + 1) * (win->max_lines - 2) - + 2) - == lines_disp) - right_margin_putp(sbar_curs, "+", langinfo, term, win, lines_disp, - win->offset); - else - right_margin_putp(sbar_line, "|", langinfo, term, win, lines_disp, - win->offset); - } + win->start = wi; - /* Print a newline character if we are not at the end of */ - /* the input nor at the end of the window */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (i < count - 1 && lines_disp < win->max_lines) - { - fputc('\n', stdout); + if (word_a[wi].str != NULL) + win->start = wi; - if (win->offset > 0) + if (win->end < count - 1) { - long i; - for (i = 0; i < win->offset; i++) - fputc(' ', stdout); + for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0; + wi++) + { + } + if (word_a[wi].str != NULL) + win->end = wi; } - - left_margin_putp(scroll_symbol, term, win); } - /* We do not increment the number of lines seen after */ - /* a premature end of input */ - /* """""""""""""""""""""""""""""""""""""""""""""""""" */ - if (i < count - 1) - lines_disp++; - - if (win->max_lines == 1) - break; - } - else if (i <= count - 1 && lines_disp == win->max_lines) + /* In column mode we need to take care of the */ + /* horizontal scrolling */ + /* """""""""""""""""""""""""""""""""""""""""" */ + if (win->col_mode || win->line_mode) { - /* The last line of the window has been displayed */ - /* """""""""""""""""""""""""""""""""""""""""""""" */ - if (display_bar && line_nb_of_word_a[i] == last_line) + long pos; + + if (word_a[current].start == 0) { - if (!toggle->no_scrollbar) + long len; + + len = term->ncolumns - 3; + pos = first_word_in_line_a[line_nb_of_word_a[current - 1]]; + + while (word_a[current - 1].end - win->first_column >= len) { - if (win->max_lines > 1) - right_margin_putp(sbar_down, "/", langinfo, term, win, lines_disp, - win->offset); - else - right_margin_putp(sbar_arr_up, "^", langinfo, term, win, - lines_disp, win->offset); + win->first_column += word_a[pos].end - word_a[pos].start + 2; + + pos++; } } - else - { - if (display_bar && !toggle->no_scrollbar) - right_margin_putp(sbar_arr_down, "v", langinfo, term, win, - lines_disp, win->offset); - break; - } + else if (word_a[current - 1].start < win->first_column) + win->first_column = word_a[current - 1].start; } - else - /* These lines were not in the widows and so we have nothing to do */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - break; + current--; } + else + break; + } while (current != old_current && !word_a[current].is_selectable); - /* Next word */ - /* """"""""" */ - i++; + if (!word_a[current].is_selectable) + { + current = old_current; + win->start = old_start; + if (win->col_mode || win->line_mode) + win->first_column = old_first_column; } - /* Update win->end, this is necessary because we only */ - /* call build_metadata on start and on terminal resize */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - if (i == count) - win->end = i - 1; - else - win->end = i; - /* We restore the cursor position saved before the display of the window */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - tputs(TPARM1(restore_cursor), 1, outch); - - /* Re-arm the periodic timer */ - /* """"""""""""""""""""""""" */ - periodic_itv.it_value.tv_sec = 0; - periodic_itv.it_value.tv_usec = TICK; - periodic_itv.it_interval.tv_sec = 0; - periodic_itv.it_interval.tv_usec = TICK; - setitimer(ITIMER_REAL, &periodic_itv, NULL); - - return lines_disp; + if (current != old_current) + *nl = disp_lines(win, toggle, current, count, search_mode, search_data, + term, last_line, tmp_word, langinfo); } -/* ============================================ */ -/* Signal handler. Manages SIGWINCH and SIGALRM */ -/* ============================================ */ -static void -sig_handler(int s) -{ - switch (s) - { - /* Standard termination signals */ - /* """""""""""""""""""""""""""" */ - case SIGSEGV: - fputs("SIGSEGV received!\n", stderr); - tputs(TPARM1(carriage_return), 1, outch); - tputs(TPARM1(cursor_visible), 1, outch); - restore_term(fileno(stdin)); - - exit(EXIT_FAILURE); +/* ====================== */ +/* Moves the cursor right */ +/* ====================== */ +void +move_right(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long last_line, char * tmp_word) +{ + long old_current = current; + long old_start = win->start; + long old_first_column = win->first_column; + long wi; /* word index */ - case SIGTERM: - case SIGHUP: - fputs("Interrupted!\n", stderr); - tputs(TPARM1(carriage_return), 1, outch); - tputs(TPARM1(cursor_visible), 1, outch); - restore_term(fileno(stdin)); + do + { + if (current < count - 1) + { + if (current == win->end) + if (win->start < count - 1 && win->end != count - 1) + { + for (wi = win->start + 1; wi < count - 1 && word_a[wi].start != 0; + wi++) + { + } - exit(EXIT_FAILURE); + if (word_a[wi].str != NULL) + win->start = wi; - /* Terminal resize */ - /* """"""""""""""" */ - case SIGWINCH: - got_winch = 1; - break; + if (win->end < count - 1) + { + for (wi = win->end + 2; wi < count - 1 && word_a[wi].start != 0; + wi++) + { + } + if (word_a[wi].str != NULL) + win->end = wi; + } + } - /* Alarm triggered, This signal is used by the search mechanism to */ - /* forces a window refresh. */ - /* The help mechanism uses it to clear the message */ - /* It is also used to redisplay the window after the end of a terminal */ - /* resizing. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - case SIGALRM: - if (timeout.initial_value > 0) - got_timeout_tick = 1; + /* In column mode we need to take care of the */ + /* horizontal scrolling */ + /* """""""""""""""""""""""""""""""""""""""""" */ + if (win->col_mode || win->line_mode) + { + if (word_a[current].is_last) + win->first_column = 0; + else + { + long pos; + long len; - if (help_timer > 0) - help_timer--; + len = term->ncolumns - 3; - if (daccess_timer > 0) - daccess_timer--; + if (word_a[current + 1].end >= len + win->first_column) + { + /* Find the first word to be displayed in this line */ + /* """""""""""""""""""""""""""""""""""""""""""""""" */ + pos = first_word_in_line_a[line_nb_of_word_a[current]]; - if (help_timer == 0 && help_mode) - got_help_alrm = 1; + while (word_a[pos].start <= win->first_column) + pos++; - if (daccess_timer == 0) - got_daccess_alrm = 1; + pos--; - if (winch_timer > 0) - winch_timer--; + /* If the new current word cannot be displayed, search */ + /* the first word in the line that can be displayed by */ + /* iterating on pos. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + while (word_a[current + 1].end - word_a[pos].start >= len) + pos++; - if (winch_timer == 0) - { - got_winch = 0; - got_help_alrm = 0; - got_winch_alrm = 1; + if (word_a[pos].start > 0) + win->first_column = word_a[pos].start; + } + } } - + current++; + } + else break; - } -} + } while (current != old_current && !word_a[current].is_selectable); -/* egetopt.c -- Extended 'getopt'. - * - * A while back, a public-domain version of getopt() was posted to the - * net. A bit later, a gentleman by the name of Keith Bostic made some - * enhancements and reposted it. - * - * In recent weeks (i.e., early-to-mid 1988) there's been some - * heated discussion in comp.lang.c about the merits and drawbacks - * of getopt(), especially with regard to its handling of '?'. - * - * In light of this, I have taken Mr. Bostic's public-domain getopt() - * and have made some changes that I hope will be considered to be - * improvements. I call this routine 'egetopt' ("Extended getopt"). - * The default behavior of this routine is the same as that of getopt(), - * but it has some optional features that make it more useful. These - * options are controlled by the settings of some global variables. - * By not setting any of these extra global variables, you will have - * the same functionality as getopt(), which should satisfy those - * purists who believe getopt() is perfect and can never be improved. - * If, on the other hand, you are someone who isn't satisfied with the - * status quo, egetopt() may very well give you the added capabilities - * you want. - * - * Look at the enclosed README file for a description of egetopt()'s - * new features. - * - * The code was originally posted to the net as getopt.c by ... - * - * Keith Bostic - * ARPA: keith@seismo - * UUCP: seismo!keith - * - * Current version: added enhancements and comments, reformatted code. - * - * Lloyd Zusman - * Master Byte Software - * Los Gatos, California - * Internet: ljz@fx.com - * UUCP: ...!ames!fxgrp!ljz - * - * May, 1988 - */ - -/* None of these constants are referenced in the executable portion of */ -/* the code ... their sole purpose is to initialize global variables. */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -#define BADCH (int)'@' -#define NEEDSEP (int)':' -#define MAYBESEP (int)'%' -#define ERRFD 2 -#define EMSG "" -#define START "-" - -/* Here are all the pertinent global variables. */ -/* """""""""""""""""""""""""""""""""""""""""""" */ -int opterr = 1; /* if true, output error message */ -int optind = 1; /* index into parent argv vector */ -int optopt; /* character checked for validity */ -int optbad = BADCH; /* character returned on error */ -int optchar = 0; /* character that begins returned option */ -int optneed = NEEDSEP; /* flag for mandatory argument */ -int optmaybe = MAYBESEP; /* flag for optional argument */ -int opterrfd = ERRFD; /* file descriptor for error text */ -char * optarg = NULL; /* argument associated with option */ -char * optstart = START; /* list of characters that start options */ - -/* Macros. */ -/* """"""" */ - -/* Conditionally print out an error message and return (depends on the */ -/* setting of 'opterr' and 'opterrfd'). Note that this version of */ -/* TELL() doesn't require the existence of stdio.h. */ -/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ -#define TELL(S) \ - { \ - if (opterr && opterrfd >= 0) \ - { \ - char option = (char)optopt; \ - dummy_rc = write(opterrfd, (S), strlen(S)); \ - dummy_rc = write(opterrfd, &option, 1); \ - dummy_rc = write(opterrfd, "\n", 1); \ - } \ - short_usage(); \ - return (optbad); \ - } - -/* Here it is: */ -/* """"""""""" */ -static int -egetopt(int nargc, char ** nargv, char * ostr) -{ - static char * place = EMSG; /* option letter processing */ - register char * oli; /* option letter list index */ - register char * osi = NULL; /* option start list index */ - - if (nargv == (char **)NULL) - return (EOF); + if (!word_a[current].is_selectable) + { + current = old_current; + win->start = old_start; + if (win->col_mode || win->line_mode) + win->first_column = old_first_column; + } - if (nargc <= optind || nargv[optind] == NULL) - return (EOF); + if (current != old_current) + *nl = disp_lines(win, toggle, current, count, search_mode, search_data, + term, last_line, tmp_word, langinfo); +} - if (place == NULL) - place = EMSG; +/* ================================================================== */ +/* Get the last word of a line after it has been formed to fit in the */ +/* terminal. */ +/* ================================================================== */ +long +get_line_last_word(long line, long last_line) +{ + if (line == last_line) + return count - 1; + else + return first_word_in_line_a[line + 1] - 1; +} - /* Update scanning pointer. */ - /* """""""""""""""""""""""" */ - if (*place == '\0') - { - place = nargv[optind]; - if (place == NULL) - return (EOF); - else if (*place == '\0') - return (EOF); - - osi = strchr(optstart, *place); - if (osi != NULL) - optchar = (int)*osi; +/* ==================================================================== */ +/* Try to locate the best word in the target line when trying to move */ +/* the cursor upward. */ +/* returns 1 if a word has been found else 0. */ +/* This function has the side effect to potentially change the value of */ +/* the variable 'current' if an adequate word is found. */ +/* ==================================================================== */ +int +find_best_word_upward(long last_word, long s, long e) +{ + int found = 0; + long index; + long cursor; - if (optind >= nargc || osi == NULL || *++place == '\0') - return (EOF); + /* Look for the first word whose start position in the line is */ + /* less or equal to the source word starting position */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + cursor = last_word; + while (word_a[cursor].start > s) + cursor--; - /* Two adjacent, identical flag characters were found. */ - /* This takes care of "--", for example. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - if (*place == place[-1]) - { - ++optind; - return (EOF); - } - } + /* In case no word is eligible, keep the cursor on */ + /* the last word */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + if (cursor == last_word && word_a[cursor].start > 0) + cursor--; - /* If the option is a separator or the option isn't in the list, */ - /* we've got an error. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - optopt = (int)*place++; - oli = strchr(ostr, optopt); - if (optopt == optneed || optopt == (int)optmaybe || oli == NULL) + /* Try to guess the best choice if we have multiple choices */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (word_a[cursor].end >= s + && word_a[cursor].end - s >= e - word_a[cursor + 1].start) + current = cursor; + else { - /* If we're at the end of the current argument, bump the */ - /* argument index. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (*place == '\0') - ++optind; - - TELL("Illegal option -- "); /* bye bye */ + if (cursor < last_word) + current = cursor + 1; + else + current = cursor; } - /* If there is no argument indicator, then we don't even try to */ - /* return an argument. */ + /* If the word is not selectable, try to find a selectable word */ + /* in the line */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - ++oli; - if (*oli == '\0' || (*oli != (char)optneed && *oli != (char)optmaybe)) + if (!word_a[current].is_selectable) { - /* If we're at the end of the current argument, bump the */ - /* argument index. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (*place == '\0') - ++optind; + index = 0; + while (word_a[current - index].start > 0 + && !word_a[current - index].is_selectable) + index++; - optarg = NULL; - } - /* If we're here, there's an argument indicator. It's handled */ - /* differently depending on whether it's a mandatory or an */ - /* optional argument. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - else - { - /* If there's no white space, use the rest of the */ - /* string as the argument. In this case, it doesn't */ - /* matter if the argument is mandatory or optional. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""" */ - if (*place != '\0') - optarg = place; - - /* If we're here, there's whitespace after the option. */ - /* */ - /* Is it a mandatory argument? If so, return the */ - /* next command-line argument if there is one. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - else if (*oli == (char)optneed) + if (word_a[current - index].is_selectable) { - /* If we're at the end of the argument list, there */ - /* isn't an argument and hence we have an error. */ - /* Otherwise, make 'optarg' point to the argument. */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (nargc <= ++optind) - { - place = EMSG; - TELL("Option requires an argument -- "); - } - else - optarg = nargv[optind]; + current -= index; + found = 1; } - /* If we're here it must have been an optional argument. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ else { - if (nargc <= ++optind) - { - place = EMSG; - optarg = NULL; - } - else - { - optarg = nargv[optind]; - if (optarg == NULL) - place = EMSG; + index = 0; + while (current + index < last_word + && !word_a[current + index].is_selectable) + index++; - /* If the next item begins with a flag */ - /* character, we treat it like a new */ - /* argument. This is accomplished by */ - /* decrementing 'optind' and returning */ - /* a null argument. */ - /* """"""""""""""""""""""""""""""""""" */ - else if (strchr(optstart, *optarg) != NULL) - { - --optind; - optarg = NULL; - } + if (word_a[current + index].is_selectable) + { + current += index; + found = 1; } } - place = EMSG; - ++optind; } + else + found = 1; - /* Return option letter. */ - /* """"""""""""""""""""" */ - return (optopt); + return found; } -/* =========================================================== */ -/* Helper function to compare to delimiters for use by ll_find */ -/* =========================================================== */ -static int -delims_cmp(const void * a, const void * b) +/* ==================================================================== */ +/* Try to locate the best word in the target line when trying to move */ +/* the cursor downward. */ +/* returns 1 if a word has been found else 0. */ +/* This function has the side effect to potentially change the value of */ +/* the variable 'current' if an adequate word is found. */ +/* ==================================================================== */ +int +find_best_word_downward(long last_word, long s, long e) { - return strcmp((const char *)a, (const char *)b); -} + int found = 0; + long index; + long cursor; -/* ========================================================= */ -/* Set new first column to display when horizontal scrolling */ -/* Alter win->first_column */ -/* ========================================================= */ -static void -set_new_first_column(win_t * win, term_t * term) -{ - long pos; + /* Look for the first word whose start position in the line is */ + /* less or equal than the source word starting position */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + cursor = last_word; + while (word_a[cursor].start > s) + cursor--; - if (word_a[current].start < win->first_column) - { - pos = current; + /* In case no word is eligible, keep the cursor on */ + /* the last word */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + if (cursor == last_word && word_a[cursor].start > 0) + cursor--; - while (win->first_column > 0 && word_a[current].start < win->first_column) + /* Try to guess the best choice if we have multiple choices */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (cursor < count - 1 + && word_a[cursor].end - s >= e - word_a[cursor + 1].start) + current = cursor; + else + { + if (cursor < count - 1) { - win->first_column = word_a[pos].start; - pos--; + if (cursor < last_word) + current = cursor + 1; + else + current = cursor; } + else + current = count - 1; } - else if (word_a[current].end - win->first_column >= term->ncolumns - 3) + + /* If the word is not selectable, try to find a selectable word */ + /* in ts line */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!word_a[current].is_selectable) { - pos = first_word_in_line_a[line_nb_of_word_a[current]]; + index = 0; + while (word_a[current - index].start > 0 + && !word_a[current - index].is_selectable) + index++; - while (!word_a[pos].is_last - && word_a[current].end - win->first_column >= term->ncolumns - 3) + if (word_a[current - index].is_selectable) { - pos++; - win->first_column = word_a[pos].start; + current -= index; + found = 1; + } + else + { + index = 0; + while (current + index < last_word + && !word_a[current + index].is_selectable) + index++; + + if (word_a[current + index].is_selectable) + { + current += index; + found = 1; + } } } + else + found = 1; + + return found; } -/* ==================================================== */ -/* Restrict the matches to word ending with the pattern */ -/* ==================================================== */ +/* =================== */ +/* Moves the cursor up */ +/* =================== */ void -select_ending_matches(win_t * win, term_t * term, search_data_t * search_data, - long * last_line) -{ - if (matches_count > 0) - { - long i; - long j = 0; - long index; - long nb; - long * tmp; - char * ptr; - char * last_mb; - int mb_len; - - /* Creation of an alternate array which will */ - /* contain only the candidates having potentially */ - /* an ending pattern, if this array becomes non */ - /* empty then it will replace the original array. */ - /* """""""""""""""""""""""""""""""""""""""""""""" */ - alt_matching_words_a = xrealloc(alt_matching_words_a, - matches_count * (sizeof(long))); +move_up(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long page, long first_selectable, long last_line, char * tmp_word) +{ + long line; /* The line being processed (the target line) */ + long start_line; /* The first line of the window */ + long cur_line; /* The line of the cursor */ + long nlines; /* Number of line in the window */ + long first_selectable_line; /* the line containing the first * + * selectable word */ + long lines_skipped; /* The number of line between the target line and the * + * first line containing a selectable word in case of * + * exclusions. */ + long last_word; /* The last word on the target line */ + long s, e; /* Starting and ending terminal position of a word */ + int found; /* 1 if a line could be fond else 0 */ - for (i = 0; i < matches_count; i++) - { - index = matching_words_a[i]; - long len = word_a[index].len; - char * str = word_a[index].str; + /* Store the initial starting and ending positions of */ + /* the word under the cursor */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + s = word_a[current].start; + e = word_a[current].end; - /* count the trailing blanks non counted in the bitmap */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - ptr = str + strlen(str); + /* Identify the line number of the first window's line */ + /* and the line number of the current line */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + start_line = line_nb_of_word_a[win->start]; + cur_line = line_nb_of_word_a[current]; + first_selectable_line = line_nb_of_word_a[first_selectable]; + lines_skipped = 0; + found = 0; + nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1; - nb = 0; - while ((ptr = mb_prev(str, ptr)) != NULL && isblank(*ptr)) - if (ptr - str + 1 > len) - nb++; - else - break; + /* initialise the target line */ + /* """""""""""""""""""""""""" */ + line = cur_line; - /* Check the bit corresponding to the last non blank glyph */ - /* If set we add the index to an alternate array, if not we */ - /* clear the bitmap of the corresponding word */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (BIT_ISSET(word_a[index].bitmap, - word_a[index].mb - nb - 1 - daccess.flength - 1)) - alt_matching_words_a[j++] = index; + /* Special case if the cursor is already in the line containing the */ + /* first selectable word. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (line == first_selectable_line) + { + /* we can't move the cursor up but we still can try to show the */ + /* more non selectable words as we can. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (line <= start_line + nlines - 1 - page) + { + /* We are scrolling one line at a time and the cursor is not in */ + /* the last line of the window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (start_line - page > 0) + /* There is enough remaining line to fill a window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''' */ + start_line -= page; else - { - /* Look if the end of the word potentially contain an */ - /* ending pattern. */ - /* """""""""""""""""""""""""""""""""""""""""""""""""" */ - if (search_mode == FUZZY) - { - mb_len = mblen(ptr, 4); - last_mb = search_data->buf + search_data->len - mb_len; - - /* in fuzzy search mode we only look the last glyph */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - if (memcmp(ptr, last_mb, mb_len) == 0) - alt_matching_words_a[j++] = index; - else - memset(word_a[index].bitmap, '\0', - (word_a[index].mb - 1 - daccess.flength) / CHAR_BIT + 1); - } - else - { - /* in not fuzzy search mode use all the pattern */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - for (nb = 0; nb < search_data->mb_len - 1; nb++) - ptr = mb_prev(str, ptr); - if (memcmp(ptr, search_data->buf, search_data->len) == 0) - alt_matching_words_a[j++] = index; - else - memset(word_a[index].bitmap, '\0', - (word_a[index].mb - 1 - daccess.flength) / CHAR_BIT + 1); - } - } + /* We cannot scroll further */ + /* '''''''''''''''''''''''' */ + start_line = 0; } - - /* Swap the normal and alt array */ - /* """"""""""""""""""""""""""""" */ - matches_count = j; - matching_words_a_size = j; - - tmp = matching_words_a; - matching_words_a = alt_matching_words_a; - alt_matching_words_a = tmp; - - if (j > 0) + else { - /* Adjust the bitmap to the ending version */ - /* """"""""""""""""""""""""""""""""""""""" */ - update_bitmaps(search_mode, search_data, END_AFFINITY); + /* The cursor is already in the last line of the windows. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (line >= nlines) + /* There is enough remaining line to fill a window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''' */ + start_line = line - nlines + 1; + else + /* We cannot scroll further */ + /* '''''''''''''''''''''''' */ + start_line = 0; + } + } + else + { + if (line - page < 0) + { + /* Trivial case, we are already on the first page */ + /* just jump to the first selectable line */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + line = first_selectable_line; + last_word = get_line_last_word(line, last_line); + find_best_word_upward(last_word, s, e); + } + else + { + /* Temporarily move up one page */ + /* """""""""""""""""""""""""""" */ + line -= page; - current = matching_words_a[0]; + /* The target line cannot be before the line containing the first */ + /* selectable word. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (line < first_selectable_line) + { + line = first_selectable_line; + last_word = get_line_last_word(line, last_line); + find_best_word_upward(last_word, s, e); + } + else + { + /* If this is not the case, search upward for the line with a */ + /* selectable word. This line is guaranteed to exist. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + while (line >= first_selectable_line) + { + last_word = get_line_last_word(line, last_line); - if (current < win->start || current > win->end) - *last_line = build_metadata(term, count, win); + if (find_best_word_upward(last_word, s, e)) + { + found = 1; + break; + } - /* Set new first column to display */ - /* """"""""""""""""""""""""""""""" */ - set_new_first_column(win, term); + line--; + lines_skipped++; + } + } } } + + /* Look if we need to adjust the window to follow the cursor */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!found && start_line - page >= 0) + { + /* We are on the first line containing a selectable word and */ + /* There is enough place to scroll up a page but only scrolls */ + /* up if the cursor remains in the window */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (start_line + nlines - line > page) + start_line -= page; + } + else if (line < start_line) + { + /* The target line is above the windows */ + /* """""""""""""""""""""""""""""""""""" */ + if (start_line - page - lines_skipped < 0) + /* There isn't enough remaining lines to scroll up */ + /* a page size. */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + start_line = 0; + else + start_line -= page + lines_skipped; + } + + /* And set the new value of the starting word of the window */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + win->start = first_word_in_line_a[start_line]; + + /* Set the new first column to display */ + /* """"""""""""""""""""""""""""""""""" */ + set_new_first_column(win, term); + + /* Redisplay the window */ + /* """""""""""""""""""" */ + *nl = disp_lines(win, toggle, current, count, search_mode, search_data, term, + last_line, tmp_word, langinfo); } -/* ====================================================== */ -/* Restrict the matches to word starting with the pattern */ -/* ====================================================== */ +/* ===================== */ +/* Moves the cursor down */ +/* ===================== */ void -select_starting_matches(win_t * win, term_t * term, search_data_t * search_data, - long * last_line) -{ - if (matches_count > 0) - { - long i; - long j = 0; - long index; - long * tmp; - long pos; - char * first_mb; - int mb_len; +move_down(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long page, long last_selectable, long last_line, char * tmp_word) +{ + long line; /* The line being processed (the target line) */ + long start_line; /* The first line of the window */ + long cur_line; /* The line of the cursor */ + long nlines; /* Number of line in the window */ + long last_selectable_line; /* the line containing the last * + * selectable word */ + long lines_skipped; /* The number of line between the target line and the * + * first line containing a selectable word in case of * + * exclusions. */ + long last_word; /* The last word on the target line */ + long s, e; /* Starting and ending terminal position of a word */ + int found; /* 1 if a line could be fond in the next page else 0 */ - alt_matching_words_a = xrealloc(alt_matching_words_a, - matches_count * (sizeof(long))); + /* Store the initial starting and ending positions of */ + /* the word under the cursor */ + /* """""""""""""""""""""""""""""""""""""""""""""""""" */ + s = word_a[current].start; + e = word_a[current].end; - first_mb = xmalloc(5); + /* Identify the line number of the first window's line */ + /* and the line number of the current line */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ + start_line = line_nb_of_word_a[win->start]; + cur_line = line_nb_of_word_a[current]; + last_selectable_line = line_nb_of_word_a[last_selectable]; + lines_skipped = 0; + found = 0; + nlines = win->max_lines < last_line + 1 ? win->max_lines : last_line + 1; - for (i = 0; i < matches_count; i++) + /* initialise the target line */ + /* """""""""""""""""""""""""" */ + line = cur_line; + + /* Special case if the cursor is already in the line containing the */ + /* last selectable word. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (line == last_selectable_line) + { + /* we can't move the cursor down but we still can try to show the */ + /* more non selectable words as we can. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (line >= start_line + page) { - index = matching_words_a[i]; - if (BIT_ISSET(word_a[index].bitmap, 0)) - alt_matching_words_a[j++] = index; + /* We are scrolling one line at a time and the cursor is not in */ + /* the first line of the window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (start_line + page + nlines - 1 <= last_line) + /* There is enough remaining line to fill a window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''' */ + start_line += page; + else + /* We cannot scroll further */ + /* '''''''''''''''''''''''' */ + start_line = last_line - nlines + 1; + } + else + { + /* The cursor is already in the first line of the windows. */ + /* ''''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + if (last_line - line + 1 > nlines) + /* There is enough remaining line to fill a window. */ + /* '''''''''''''''''''''''''''''''''''''''''''''''' */ + start_line = line; + else + /* We cannot scroll further */ + /* '''''''''''''''''''''''' */ + start_line = last_line - nlines + 1; + } + } + else + { + /* The cursor is above the line containing the last selectable word. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (last_line - line - page < 0) + { + /* Trivial case, we are already on the last page */ + /* just jump to the last selectable line */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + line = last_selectable_line; + last_word = get_line_last_word(line, last_line); + find_best_word_downward(last_word, s, e); + } + else + { + /* Temporarily move down one page */ + /* """""""""""""""""""""""""""""" */ + line += page; + + /* The target line cannot be before the line containing the first */ + /* selectable word. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (line > last_selectable_line) + { + line = last_selectable_line; + last_word = get_line_last_word(line, last_line); + find_best_word_downward(last_word, s, e); + } else { - if (search_mode == FUZZY) + /* If this is not the case, search upward for the line with a */ + /* selectable word. This line is guaranteed to exist. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + while (line <= last_selectable_line) { - first_mb = mb_strprefix(first_mb, word_a[index].str, 1, &pos); - mb_len = pos; + last_word = get_line_last_word(line, last_line); - /* in fuzzy search mode we only look the first glyph */ - /* """"""""""""""""""""""""""""""""""""""""""""""""" */ - if (memcmp(search_data->buf, first_mb, mb_len) == 0) - alt_matching_words_a[j++] = index; - else - memset(word_a[index].bitmap, '\0', - (word_a[index].mb - 1 - daccess.flength) / CHAR_BIT + 1); - } - else - { - /* in not fuzzy search mode use all the pattern */ - /* """""""""""""""""""""""""""""""""""""""""""" */ - if (memcmp(search_data->buf, word_a[index].str, search_data->len) - == 0) - alt_matching_words_a[j++] = index; - else - memset(word_a[index].bitmap, '\0', - (word_a[index].mb - 1 - daccess.flength) / CHAR_BIT + 1); + if (find_best_word_downward(last_word, s, e)) + { + found = 1; + break; + } + + line++; + lines_skipped++; } } } - free(first_mb); - - matches_count = j; - matching_words_a_size = j; - - tmp = matching_words_a; - matching_words_a = alt_matching_words_a; - alt_matching_words_a = tmp; - - if (j > 0) + /* Look if we need to adjust the window to follow the cursor */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!found && start_line + nlines - 1 + page <= last_line) { - /* Adjust the bitmap to the ending version */ - /* """"""""""""""""""""""""""""""""""""""" */ - update_bitmaps(search_mode, search_data, START_AFFINITY); + /* We are on the last line containing a selectable word and */ + /* There is enough place to scroll down a page but only scrolls */ + /* down if the cursor remains in the window */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (line - start_line >= page) + start_line += page; + } + else if (line > start_line + nlines - 1) + { + /* The target line is below the windows */ + /* """""""""""""""""""""""""""""""""""" */ + if (start_line + nlines + page + lines_skipped - 1 > last_line) + /* There isn't enough remaining lines to scroll down */ + /* a page size. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""" */ + start_line = last_line - nlines + 1; + else + start_line += page + lines_skipped; + } + } - current = matching_words_a[0]; + /* And set the new value of the starting word of the window */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + win->start = first_word_in_line_a[start_line]; - if (current < win->start || current > win->end) - *last_line = build_metadata(term, count, win); + /* Set the new first column to display */ + /* """"""""""""""""""""""""""""""""""" */ + set_new_first_column(win, term); - /* Set new first column to display */ - /* """"""""""""""""""""""""""""""" */ - set_new_first_column(win, term); - } - } + /* Redisplay the window */ + /* """""""""""""""""""" */ + *nl = disp_lines(win, toggle, current, count, search_mode, search_data, term, + last_line, tmp_word, langinfo); } /* ================ */ @@ -6804,8 +4909,6 @@ long last_selectable; /* Index of the last selectable word in the input * * stream */ - long s, e; /* word variable to contain the starting and ending * - * terminal position of a word */ long min_size; /* Minimum screen width of a column in tabular mode */ long tab_max_size; /* Maximum screen width of a column in tabular * @@ -6823,8 +4926,8 @@ * -w and -c are both set */ long cols_max_size = 0; /* Same as above for the columns widths */ - long col_index = 0; /* Index of the current column when reading words,i * - * used in column mode */ + long col_index = 0; /* Index of the current column when reading words,i * + * used in column mode */ long cols_number = 0; /* Number of columns in column mode */ char * pre_selection_index = NULL; /* pattern used to set the initial * @@ -6832,11 +4935,11 @@ unsigned char * buffer = xmalloc(16); /* Input buffer */ search_data_t search_data; - search_data.buf = NULL; /* Search buffer */ - search_data.len = 0; /* Current position in the search buffer */ - search_data.mb_len = 0; /* Current position in the search buffer in * - * multibyte units */ - search_data.fuzzy_err = 0; /* reset the error indicator */ + search_data.buf = NULL; /* Search buffer */ + search_data.len = 0; /* Current position in the search buffer */ + search_data.utf8_len = 0; /* Current position in the search buffer in * + * UTF-8 units */ + search_data.fuzzy_err = 0; /* reset the error indicator */ search_data.fuzzy_err_pos = -1; /* no last error position in search buffer */ long matching_word_cur_index = -1; /* cache for the next/previous moves * @@ -6848,8 +4951,8 @@ ll_t * word_delims_list = NULL; ll_t * record_delims_list = NULL; - char mb_buffer[5]; /* buffer to store the bytes of a multibyte character * - * (4 chars max) */ + char utf8_buffer[5]; /* buffer to store the bytes of a UTF-8 glyph * + * (4 chars max) */ unsigned char is_last; char * charset; @@ -6906,6 +5009,7 @@ win.cursor_on_tag_attr = init_attr; win.bar_attr = init_attr; win.shift_attr = init_attr; + win.message_attr = init_attr; win.search_field_attr = init_attr; win.search_text_attr = init_attr; win.search_err_field_attr = init_attr; @@ -7007,7 +5111,7 @@ if (!is_supported_charset) { - fprintf(stderr, "%s: %s\n", "Unsupported charset", charset); + fprintf(stderr, "%s is not a supported charset.", charset); exit(EXIT_FAILURE); } @@ -7025,9 +5129,13 @@ else my_isprint = isprint7; - /* Set terminal in noncanonical, noecho mode */ - /* """"""""""""""""""""""""""""""""""""""""" */ - setupterm((char *)0, 1, (int *)0); + /* Set terminal in noncanonical, noecho mode and */ + /* if TERM is unset or unknown, vt100 is assumed. */ + /* """""""""""""""""""""""""""""""""""""""""""""" */ + if (getenv("TERM") == NULL) + setupterm("vt100", 1, (int *)0); + else + setupterm((char *)0, 1, (int *)0); /* Get some terminal capabilities */ /* """""""""""""""""""""""""""""" */ @@ -7056,37 +5164,50 @@ daccess.length = -2; daccess.flength = 0; daccess.offset = 0; + daccess.plus = 0; daccess.size = 0; daccess.ignore = 0; daccess.follow = 'y'; daccess.num_sep = NULL; daccess.def_number = -1; + /* Allowed command line options */ + /* """""""""""""""""""""""""""" */ + static char * optstring = "Vf:h?X:x:qdMba:i:e:S:I:E:A:Z:1:2:3:4:5:C:R:" + "kvclwrg%n%t%m:s:W:L:T%P%pN%U%FD:/:"; + /* Command line options analysis */ /* """"""""""""""""""""""""""""" */ - while ((opt = egetopt(argc, argv, - "Vf:h?X:x:qdMba:i:e:S:I:E:A:Z:1:2:3:4:5:C:R:" - "kvclwrg%n:t%m:s:W:L:T%P%pN%U%FD:/:")) - != -1) + while ((opt = egetopt(argc, argv, optstring)) != -1) { switch (opt) { case 'V': fputs("Version: " VERSION "\n", stdout); - exit(0); + exit(EXIT_SUCCESS); case 'f': - if (optarg && *optarg != '-') - custom_ini_file = xstrdup(optarg); + if (eoptarg && *eoptarg != '-') + custom_ini_file = xstrdup(eoptarg); else + { TELL("Option requires an argument -- "); + short_usage(1); + } + break; case 'n': - if (optarg && *optarg != '-') - win.asked_max_lines = abs(atoi(optarg)); + if (eoptarg != NULL) + { + if (sscanf(eoptarg, "%ld", &(win.asked_max_lines)) != 1) + { + TELL("Argument must be numeric -- "); + short_usage(1); + } + } else - TELL("Option requires an argument -- "); + win.asked_max_lines = 0; break; case 'd': @@ -7098,23 +5219,32 @@ break; case 's': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - pre_selection_index = xstrdup(optarg); - mb_interpret(pre_selection_index, &langinfo); + pre_selection_index = xstrdup(eoptarg); + utf8_interpret(pre_selection_index, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 't': - if (optarg != NULL) + if (eoptarg != NULL) { - if (sscanf(optarg, "%ld", &(win.max_cols)) != 1) + if (sscanf(eoptarg, "%ld", &(win.max_cols)) != 1) + { TELL("Argument must be numeric -- "); + short_usage(1); + } if (win.max_cols < 1) + { TELL("Argument must be at least 1 -- "); + short_usage(1); + } } win.tab_mode = 1; @@ -7148,7 +5278,7 @@ if (win.col_sep) break; - if (optarg == NULL) + if (eoptarg == NULL) { win.gutter_a = xmalloc(1 * sizeof(char *)); @@ -7167,23 +5297,23 @@ int mblen; char * gutter; - gutter = xstrdup(optarg); + gutter = xstrdup(eoptarg); - mb_interpret(gutter, &langinfo); /* Guarantees a well formed * - * UTF-8 string */ + utf8_interpret(gutter, &langinfo); /* Guarantees a well formed * + * UTF-8 string */ - win.gutter_nb = mb_strlen(gutter); + win.gutter_nb = utf8_strlen(gutter); win.gutter_a = xmalloc(win.gutter_nb * sizeof(char *)); offset = 0; for (i = 0; i < win.gutter_nb; i++) { - mblen = mb_get_length(*(gutter + offset)); + mblen = utf8_get_length(*(gutter + offset)); win.gutter_a[i] = xcalloc(1, mblen + 1); memcpy(win.gutter_a[i], gutter + offset, mblen); - n = wcswidth((w = mb_strtowcs(win.gutter_a[i])), 1); + n = wcswidth((w = utf8_strtowcs(win.gutter_a[i])), 1); free(w); if (n > 1) @@ -7197,6 +5327,7 @@ free(gutter); TELL("A multi columns gutter is not allowed -- "); + short_usage(1); } offset += mblen; @@ -7213,15 +5344,18 @@ break; case 'm': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - message = xstrdup(optarg); + message = xstrdup(eoptarg); if (!langinfo.utf8) - mb_sanitize(message); - mb_interpret(message, &langinfo); + utf8_sanitize(message); + utf8_interpret(message, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'r': @@ -7233,7 +5367,7 @@ break; case 'i': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { /* Set the default behaviour if not already set */ /* """""""""""""""""""""""""""""""""""""""""""" */ @@ -7241,16 +5375,19 @@ pattern_def_include = 0; if (include_pattern == NULL) - include_pattern = concat("(", optarg, ")", NULL); + include_pattern = concat("(", eoptarg, ")", NULL); else - include_pattern = concat(include_pattern, "|(", optarg, ")", NULL); + include_pattern = concat(include_pattern, "|(", eoptarg, ")", NULL); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'e': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { /* Set the default behaviour if not already set */ /* """""""""""""""""""""""""""""""""""""""""""" */ @@ -7258,41 +5395,50 @@ pattern_def_include = 1; if (exclude_pattern == NULL) - exclude_pattern = concat("(", optarg, ")", NULL); + exclude_pattern = concat("(", eoptarg, ")", NULL); else - exclude_pattern = concat(exclude_pattern, "|(", optarg, ")", NULL); + exclude_pattern = concat(exclude_pattern, "|(", eoptarg, ")", NULL); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'C': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { if (cols_selector_list == NULL) cols_selector_list = ll_new(); - ll_append(cols_selector_list, xstrdup(optarg)); + ll_append(cols_selector_list, xstrdup(eoptarg)); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'R': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { if (rows_selector_list == NULL) rows_selector_list = ll_new(); - ll_append(rows_selector_list, xstrdup(optarg)); + ll_append(rows_selector_list, xstrdup(eoptarg)); win.max_cols = 0; } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'S': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { sed_t * sed_node; @@ -7300,17 +5446,20 @@ sed_list = ll_new(); sed_node = xmalloc(sizeof(sed_t)); - sed_node->pattern = xstrdup(optarg); - mb_interpret(sed_node->pattern, &langinfo); + sed_node->pattern = xstrdup(eoptarg); + utf8_interpret(sed_node->pattern, &langinfo); sed_node->stop = 0; ll_append(sed_list, sed_node); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'I': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { sed_t * sed_node; @@ -7318,17 +5467,20 @@ include_sed_list = ll_new(); sed_node = xmalloc(sizeof(sed_t)); - sed_node->pattern = xstrdup(optarg); - mb_interpret(sed_node->pattern, &langinfo); + sed_node->pattern = xstrdup(eoptarg); + utf8_interpret(sed_node->pattern, &langinfo); sed_node->stop = 0; ll_append(include_sed_list, sed_node); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'E': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { sed_t * sed_node; @@ -7336,13 +5488,16 @@ exclude_sed_list = ll_new(); sed_node = xmalloc(sizeof(sed_t)); - sed_node->pattern = xstrdup(optarg); - mb_interpret(sed_node->pattern, &langinfo); + sed_node->pattern = xstrdup(eoptarg); + utf8_interpret(sed_node->pattern, &langinfo); sed_node->stop = 0; ll_append(exclude_sed_list, sed_node); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case '1': @@ -7350,24 +5505,27 @@ case '3': case '4': case '5': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { long count = 1; attr_t attr = init_attr; - special_pattern[opt - '1'] = xstrdup(optarg); - mb_interpret(special_pattern[opt - '1'], &langinfo); + special_pattern[opt - '1'] = xstrdup(eoptarg); + utf8_interpret(special_pattern[opt - '1'], &langinfo); /* Parse optional additional arguments */ /* """"""""""""""""""""""""""""""""""" */ - while (argv[optind] && *argv[optind] != '-') + while (argv[eoptind] && *argv[eoptind] != '-') { if (count > 2) + { TELL("Too many arguments -- "); + short_usage(1); + } /* Colors must respect the format: / */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (parse_attr(argv[optind], &attr, term.colors)) + if (parse_attr(argv[eoptind], &attr, term.colors)) { win.special_attr[opt - '1'].is_set = FORCED; win.special_attr[opt - '1'].fg = attr.fg; @@ -7380,18 +5538,24 @@ win.special_attr[opt - '1'].italic = attr.italic; } else + { TELL("Bad optional color settings -- "); + short_usage(1); + } - optind++; + eoptind++; count++; } } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'a': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { long i; /* loop index */ long offset = 0; /* nb of chars to ship to find the attribute * @@ -7407,6 +5571,7 @@ int cur_attr_set = 0; /* highlighted word (cursor) */ int bar_attr_set = 0; /* scroll bar */ int shift_attr_set = 0; /* hor. scrolling arrows */ + int message_attr_set = 0; /* message (title) */ int tag_attr_set = 0; /* selected (tagged) words */ int cursor_on_tag_attr_set = 0; /* selected words under the cursor */ int sf_attr_set = 0; /* currently searched field color */ @@ -7426,75 +5591,87 @@ int * flag; char * prefix; int prefix_len; - } attr_infos[] = { - { &win.exclude_attr, "The exclude attribute is already set -- ", - &exc_attr_set, "e:", 2 }, - { &win.include_attr, "The include attribute is already set -- ", - &inc_attr_set, "i:", 2 }, - { &win.cursor_attr, "The cursor attribute is already set -- ", - &cur_attr_set, "c:", 2 }, - { &win.bar_attr, "The scroll bar attribute is already set -- ", - &bar_attr_set, "b:", 2 }, - { &win.shift_attr, "The shift attribute is already set -- ", - &shift_attr_set, "s:", 2 }, - { &win.tag_attr, "The tag attribute is already set -- ", - &tag_attr_set, "t:", 2 }, - { &win.cursor_on_tag_attr, - "The cursor on tagged word attribute is already set -- ", - &cursor_on_tag_attr_set, "ct:", 3 }, - { &win.search_field_attr, - "The search field attribute is already set -- ", &sf_attr_set, - "sf:", 3 }, - { &win.search_text_attr, - "The search text attribute is already set -- ", &st_attr_set, - "st:", 3 }, - { &win.search_err_field_attr, - "The search with error field attribute is already set -- ", - &sf_attr_set, "sfe:", 4 }, - { &win.search_err_text_attr, - "The search text with error attribute is already set -- ", - &st_attr_set, "ste:", 4 }, - { &win.match_field_attr, - "The matching word field attribute is already set -- ", - &mf_attr_set, "mf:", 3 }, - { &win.match_text_attr, - "The matching word text attribute is already set -- ", - &mt_attr_set, "mt:", 3 }, - { &win.match_err_field_attr, - "The matching word with error field attribute is already set -- ", - &mf_attr_set, "mfe:", 4 }, - { &win.match_err_text_attr, - "The matching word with error text attribute is already set -- ", - &mt_attr_set, "mte:", 4 }, - { &win.daccess_attr, - "The direct access tag attribute is already set -- ", - &daccess_attr_set, "da:", 3 }, - { NULL, NULL, NULL, NULL, 0 } - }; + } attr_infos[] = + { { &win.exclude_attr, "The exclude attribute is already set -- ", + &exc_attr_set, "e:", 2 }, + { &win.include_attr, "The include attribute is already set -- ", + &inc_attr_set, "i:", 2 }, + { &win.cursor_attr, "The cursor attribute is already set -- ", + &cur_attr_set, "c:", 2 }, + { &win.bar_attr, "The scroll bar attribute is already set -- ", + &bar_attr_set, "b:", 2 }, + { &win.shift_attr, "The shift attribute is already set -- ", + &shift_attr_set, "s:", 2 }, + { &win.message_attr, "The message attribute is already set -- ", + &message_attr_set, "m:", 2 }, + { &win.tag_attr, "The tag attribute is already set -- ", + &tag_attr_set, "t:", 2 }, + { &win.cursor_on_tag_attr, + "The cursor on tagged word attribute is already set -- ", + &cursor_on_tag_attr_set, "ct:", 3 }, + { &win.search_field_attr, + "The search field attribute is already set -- ", &sf_attr_set, + "sf:", 3 }, + { &win.search_text_attr, + "The search text attribute is already set -- ", &st_attr_set, + "st:", 3 }, + { &win.search_err_field_attr, + "The search with error field attribute is already set -- ", + &sf_attr_set, "sfe:", 4 }, + { &win.search_err_text_attr, + "The search text with error attribute is already set -- ", + &st_attr_set, "ste:", 4 }, + { &win.match_field_attr, + "The matching word field attribute is already set -- ", + &mf_attr_set, "mf:", 3 }, + { &win.match_text_attr, + "The matching word text attribute is already set -- ", + &mt_attr_set, "mt:", 3 }, + { &win.match_err_field_attr, + "The matching word with error field attribute is already set " + "-- ", + &mf_attr_set, "mfe:", 4 }, + { &win.match_err_text_attr, + "The matching word with error text attribute is already set " + "-- ", + &mt_attr_set, "mte:", 4 }, + { &win.daccess_attr, + "The direct access tag attribute is already set -- ", + &daccess_attr_set, "da:", 3 }, + { NULL, NULL, NULL, NULL, 0 } }; - optind--; + eoptind--; - if (*argv[optind] == '-') + if (*argv[eoptind] == '-') + { TELL("A blank is required before the first sub-option -- "); + short_usage(1); + } /* Parse the arguments arguments */ /* """"""""""""""""""""""""""""" */ - while (argv[optind] && *argv[optind] != '-') + while (argv[eoptind] && *argv[eoptind] != '-') { attr = init_attr; - if (strlen(argv[optind]) < 3) + if (strlen(argv[eoptind]) < 3) + { TELL("Empty attribute value -- "); + short_usage(1); + } i = 0; while (attr_infos[i].flag != NULL) { - if (strncmp(argv[optind], attr_infos[i].prefix, + if (strncmp(argv[eoptind], attr_infos[i].prefix, attr_infos[i].prefix_len) == 0) { if (*attr_infos[i].flag) + { TELL(attr_infos[i].msg); + short_usage(1); + } attr_to_set = attr_infos[i].attr; *attr_infos[i].flag = 1; @@ -7505,12 +5682,15 @@ i++; } if (attr_infos[i].flag == NULL) + { TELL("Bad attribute prefix -- "); + short_usage(1); + } /* Attributes must respect the format: */ /* /, */ /* """"""""""""""""""""""""""""""""""" */ - if (parse_attr(argv[optind] + offset, &attr, term.colors)) + if (parse_attr(argv[eoptind] + offset, &attr, term.colors)) { attr_to_set->is_set = FORCED; attr_to_set->fg = attr.fg; @@ -7523,53 +5703,71 @@ attr_to_set->italic = attr.italic; } else + { TELL("Bad attribute settings -- "); + short_usage(1); + } - optind++; + eoptind++; } } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'X': quiet_timeout = 1; case 'x': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - if (strprefix("current", optarg)) + if (strprefix("current", eoptarg)) timeout.mode = CURRENT; - else if (strprefix("quit", optarg)) + else if (strprefix("quit", eoptarg)) timeout.mode = QUIT; - else if (strprefix("word", optarg)) + else if (strprefix("word", eoptarg)) { - if (argv[optind] && *argv[optind] != '-') + if (argv[eoptind] && *argv[eoptind] != '-') { timeout.mode = WORD; - timeout_word = argv[optind]; - mb_interpret(timeout_word, &langinfo); - optind++; + timeout_word = argv[eoptind]; + utf8_interpret(timeout_word, &langinfo); + eoptind++; } else + { TELL("Missing timeout word -- "); + short_usage(1); + } } else + { TELL("Invalid timeout type -- "); + short_usage(1); + } - if (argv[optind] && *argv[optind] != '-') + if (argv[eoptind] && *argv[eoptind] != '-') { - if (sscanf(argv[optind], "%5u", &timeout.initial_value) == 1) + if (sscanf(argv[eoptind], "%5u", &timeout.initial_value) == 1) { timeout.initial_value *= FREQ; timeout.remain = timeout.initial_value; } else + { TELL("Invalid timeout delay -- "); + short_usage(1); + } } else + { TELL("Missing timeout delay -- "); + short_usage(1); + } - optind++; + eoptind++; } break; @@ -7578,53 +5776,65 @@ break; case 'A': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - first_word_pattern = xstrdup(optarg); - mb_interpret(first_word_pattern, &langinfo); + first_word_pattern = xstrdup(eoptarg); + utf8_interpret(first_word_pattern, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'Z': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - last_word_pattern = xstrdup(optarg); - mb_interpret(last_word_pattern, &langinfo); + last_word_pattern = xstrdup(eoptarg); + utf8_interpret(last_word_pattern, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'W': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - iws = xstrdup(optarg); - mb_interpret(iws, &langinfo); + iws = xstrdup(eoptarg); + utf8_interpret(iws, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'L': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { - ils = xstrdup(optarg); - mb_interpret(ils, &langinfo); + ils = xstrdup(eoptarg); + utf8_interpret(ils, &langinfo); } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'P': toggle.pinable = 1; case 'T': toggle.taggable = 1; - if (optarg != NULL) + if (eoptarg != NULL) { - win.sel_sep = xstrdup(optarg); - mb_interpret(win.sel_sep, &langinfo); + win.sel_sep = xstrdup(eoptarg); + utf8_interpret(win.sel_sep, &langinfo); } break; @@ -7633,7 +5843,7 @@ break; case 'D': - if (optarg && *optarg != '-') + if (eoptarg && *eoptarg != '-') { int pos; wchar_t * w; @@ -7641,189 +5851,275 @@ /* Parse optional additional arguments */ /* """"""""""""""""""""""""""""""""""" */ - optind--; + eoptind--; - if (*argv[optind] == '-') + if (*argv[eoptind] == '-') + { TELL("A blank is required before the first sub-option -- "); + short_usage(1); + } - while (argv[optind] && *argv[optind] != '-') + while (argv[eoptind] && *argv[eoptind] != '-') { - if (argv[optind][1] != ':') + if (argv[eoptind][1] != ':') + { TELL("Bad format -- "); + short_usage(1); + } - switch (*(argv[optind])) + switch (*(argv[eoptind])) { case 'l': /* left char */ free(daccess.left); - daccess.left = xstrdup(argv[optind] + 2); - mb_interpret(daccess.left, &langinfo); + daccess.left = xstrdup(argv[eoptind] + 2); + utf8_interpret(daccess.left, &langinfo); - if (mb_strlen(daccess.left) != 1) + if (utf8_strlen(daccess.left) != 1) + { TELL("Too many characters after l: -- "); + short_usage(1); + } - n = wcswidth((w = mb_strtowcs(daccess.left)), 1); + n = wcswidth((w = utf8_strtowcs(daccess.left)), 1); free(w); if (n > 1) - TELL( - "A multi columns character is not allowed " - "after l: -- "); + { + TELL("A multi columns character is not allowed " + "after l: -- "); + short_usage(1); + } break; case 'r': /* right char */ free(daccess.right); - daccess.right = xstrdup(argv[optind] + 2); - mb_interpret(daccess.right, &langinfo); + daccess.right = xstrdup(argv[eoptind] + 2); + utf8_interpret(daccess.right, &langinfo); - if (mb_strlen(daccess.right) != 1) + if (utf8_strlen(daccess.right) != 1) + { TELL("Too many characters after r: -- "); + short_usage(1); + } - n = wcswidth((w = mb_strtowcs(daccess.right)), 1); + n = wcswidth((w = utf8_strtowcs(daccess.right)), 1); free(w); if (n > 1) - TELL( - "A multi columns character is not allowed " - "after r: -- "); + { + TELL("A multi columns character is not allowed " + "after r: -- "); + short_usage(1); + } break; case 'a': /* alignment */ - if (strprefix("left", argv[optind] + 2)) + if (strprefix("left", argv[eoptind] + 2)) daccess.alignment = 'l'; - else if (strprefix("right", argv[optind] + 2)) + else if (strprefix("right", argv[eoptind] + 2)) daccess.alignment = 'r'; else + { TELL("Bad format -- "); + short_usage(1); + } break; case 'p': /* padding */ - if (strprefix("all", argv[optind] + 2)) + if (strprefix("all", argv[eoptind] + 2)) daccess.padding = 'a'; - else if (strprefix("included", argv[optind] + 2)) + else if (strprefix("included", argv[eoptind] + 2)) daccess.padding = 'i'; else + { TELL("Bad format -- "); + short_usage(1); + } break; case 'w': /* width */ - if (sscanf(argv[optind] + 2, "%d%n", &daccess.length, &pos) + if (sscanf(argv[eoptind] + 2, "%d%n", &daccess.length, &pos) != 1) + { TELL("Bad format -- "); - if (argv[optind][pos + 2] != '\0') + short_usage(1); + } + if (argv[eoptind][pos + 2] != '\0') + { TELL("Bad format -- "); + short_usage(1); + } if (daccess.length <= 0 || daccess.length > 5) + { TELL("w suboption must be between 1 and 5 -- "); + short_usage(1); + } break; case 'o': /* start offset */ - if (sscanf(argv[optind] + 2, "%zu%n", &daccess.offset, &pos) - != 1) - TELL("Bad format -- "); - if (argv[optind][pos + 2] != '\0') + if (sscanf(argv[eoptind] + 2, "%zu%n+", &daccess.offset, &pos) + == 1) + { + if (argv[eoptind][pos + 2] == '+') + { + daccess.plus = 1; + + if (argv[eoptind][pos + 3] != '\0') + { + TELL("Bad format -- "); + short_usage(1); + } + } + else if (argv[eoptind][pos + 2] != '\0') + { + TELL("Bad format -- "); + short_usage(1); + } + } + else + { TELL("Bad format -- "); + short_usage(1); + } + break; - case 'n': /* numbor of digits to extract */ - if (sscanf(argv[optind] + 2, "%d%n", &daccess.size, &pos) != 1) + case 'n': /* number of digits to extract */ + if (sscanf(argv[eoptind] + 2, "%d%n", &daccess.size, &pos) != 1) + { TELL("Bad format -- "); - if (argv[optind][pos + 2] != '\0') + short_usage(1); + } + if (argv[eoptind][pos + 2] != '\0') + { TELL("Bad format -- "); + short_usage(1); + } if (daccess.size <= 0 || daccess.size > 5) + { TELL("n suboption must be between 1 and 5 -- "); + short_usage(1); + } break; - case 'i': /* Number of multibytes to ignore after the - * selector * to extract */ - if (sscanf(argv[optind] + 2, "%zu%n", &daccess.ignore, &pos) + case 'i': /* Number of UTF-8 glyphs to ignore after the * + * selector to extract */ + if (sscanf(argv[eoptind] + 2, "%zu%n", &daccess.ignore, &pos) != 1) + { TELL("Bad format -- "); - if (argv[optind][pos + 2] != '\0') + short_usage(1); + } + if (argv[eoptind][pos + 2] != '\0') + { TELL("Bad format -- "); + short_usage(1); + } break; case 'f': /* follow */ - if (strprefix("yes", argv[optind] + 2)) + if (strprefix("yes", argv[eoptind] + 2)) daccess.follow = 'y'; - else if (strprefix("no", argv[optind] + 2)) + else if (strprefix("no", argv[eoptind] + 2)) daccess.follow = 'n'; else + { TELL("Bad format -- "); + short_usage(1); + } break; case 'd': /* decorate */ free(daccess.num_sep); - daccess.num_sep = xstrdup(argv[optind] + 2); - mb_interpret(daccess.num_sep, &langinfo); + daccess.num_sep = xstrdup(argv[eoptind] + 2); + utf8_interpret(daccess.num_sep, &langinfo); - if (mb_strlen(daccess.num_sep) != 1) + if (utf8_strlen(daccess.num_sep) != 1) + { TELL("Too many characters after d: -- "); + short_usage(1); + } - n = wcswidth((w = mb_strtowcs(daccess.num_sep)), 1); + n = wcswidth((w = utf8_strtowcs(daccess.num_sep)), 1); free(w); if (n > 1) - TELL( - "A multi columns separator is not allowed " - "after d: -- "); + { + TELL("A multi columns separator is not allowed " + "after d: -- "); + short_usage(1); + } break; case 's': /* start index */ { long pos; - if (sscanf(argv[optind] + 2, "%ld%ln", &daccess_index, &pos) + if (sscanf(argv[eoptind] + 2, "%ld%ln", &daccess_index, &pos) == 1) { - if (daccess_index < 0 || *(argv[optind] + 2 + pos) != '\0') + if (daccess_index < 0 || *(argv[eoptind] + 2 + pos) != '\0') daccess_index = 1; } else + { TELL("Invalid first index after s: -- "); + short_usage(1); + } } break; case 'h': /* head */ - if (strprefix("trim", argv[optind] + 2)) + if (strprefix("trim", argv[eoptind] + 2)) daccess.head = 't'; - else if (strprefix("cut", argv[optind] + 2)) + else if (strprefix("cut", argv[eoptind] + 2)) daccess.head = 'c'; - else if (strprefix("keep", argv[optind] + 2)) + else if (strprefix("keep", argv[eoptind] + 2)) daccess.head = 'k'; else + { TELL("Bad format -- "); + short_usage(1); + } break; default: + { TELL("Bad format -- "); + short_usage(1); + } } if (daccess.length <= 0 || daccess.length > 5) daccess.length = -2; /* special value -> auto */ - optind++; + eoptind++; } } else + { TELL("Option requires an argument -- "); + short_usage(1); + } break; case 'N': - if (optarg == NULL) - optarg = "."; - else if (*optarg == '\0') - optarg = "."; + if (eoptarg == NULL) + eoptarg = "."; + else if (*eoptarg == '\0') + eoptarg = "."; if (daccess_np == NULL) { - daccess_np = concat("(", optarg, ")", NULL); + daccess_np = concat("(", eoptarg, ")", NULL); daccess.mode |= DA_TYPE_AUTO; /* auto */ } else - daccess_np = concat(daccess_np, "|(", optarg, ")", NULL); + daccess_np = concat(daccess_np, "|(", eoptarg, ")", NULL); if (daccess.def_number < 0) daccess.def_number = 0; @@ -7831,18 +6127,18 @@ break; case 'U': - if (optarg == NULL) - optarg = "."; - else if (*optarg == '\0') - optarg = "."; + if (eoptarg == NULL) + eoptarg = "."; + else if (*eoptarg == '\0') + eoptarg = "."; if (daccess_up == NULL) { - daccess_up = concat("(", optarg, ")", NULL); + daccess_up = concat("(", eoptarg, ")", NULL); daccess.mode |= DA_TYPE_AUTO; /* auto */ } else - daccess_up = concat(daccess_up, "|(", optarg, ")", NULL); + daccess_up = concat(daccess_up, "|(", eoptarg, ")", NULL); if (daccess.def_number < 0) daccess.def_number = 1; @@ -7855,40 +6151,41 @@ break; case '/': - if (strprefix("prefix", optarg)) + if (strprefix("prefix", eoptarg)) misc.default_search_method = PREFIX; - else if (strprefix("fuzzy", optarg)) + else if (strprefix("fuzzy", eoptarg)) misc.default_search_method = FUZZY; - else if (strprefix("substring", optarg)) + else if (strprefix("substring", eoptarg)) misc.default_search_method = SUBSTRING; else + { TELL("Bad format -- "); + short_usage(1); + } break; case '?': - fputc('\n', stderr); - short_usage(); - - exit(EXIT_FAILURE); + short_usage(1); case 'h': usage(); - exit(EXIT_FAILURE); + case BADCH: + short_usage(1); default: exit(EXIT_FAILURE); } - optarg = NULL; + eoptarg = NULL; } - if (optind < argc) + if (eoptind < argc) { if (argv[argc - 1][0] == '-') { fprintf(stderr, "Not an option -- %s\n", argv[argc - 1]); - short_usage(); + short_usage(1); exit(EXIT_FAILURE); } @@ -7897,7 +6194,7 @@ if (input_file == NULL) { fprintf(stderr, "Cannot open \"%s\"\n", argv[argc - 1]); - short_usage(); + short_usage(1); exit(EXIT_FAILURE); } @@ -7909,10 +6206,10 @@ /* the inclusion and exclusion patterns. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (include_pattern != NULL) - mb_interpret(include_pattern, &langinfo); + utf8_interpret(include_pattern, &langinfo); if (exclude_pattern != NULL) - mb_interpret(exclude_pattern, &langinfo); + utf8_interpret(exclude_pattern, &langinfo); /* Force the right modes when the -C option is given */ /* """"""""""""""""""""""""""""""""""""""""""""""""" */ @@ -7949,12 +6246,13 @@ void sig_handler(int s); - /* Ignore SIGTTIN and SIGTTOU */ - /* """""""""""""""""""""""""" */ + /* Ignore SIGTTIN and SIGINT */ + /* """"""""""""""""""""""""" */ sigset_t sigs, oldsigs; sigemptyset(&sigs); sigaddset(&sigs, SIGTTIN); + sigaddset(&sigs, SIGINT); sigprocmask(SIG_BLOCK, &sigs, &oldsigs); sa.sa_handler = sig_handler; @@ -8016,9 +6314,8 @@ || !term.has_cursor_right || !term.has_save_cursor || !term.has_restore_cursor) { - fprintf(stderr, - "The terminal does not have the required cursor " - "management capabilities.\n"); + fprintf(stderr, "The terminal does not have the required cursor " + "management capabilities.\n"); exit(EXIT_FAILURE); } @@ -8034,8 +6331,8 @@ } else { - /* Build the full path of the ini file */ - /* """"""""""""""""""""""""""""""""""" */ + /* Build the full path of the .ini file */ + /* """""""""""""""""""""""""""""""""""" */ home_ini_file = make_ini_path(argv[0], "HOME"); local_ini_file = make_ini_path(argv[0], "PWD"); @@ -8109,6 +6406,21 @@ win.shift_attr.is_set = SET; } + if (!win.message_attr.is_set) + { + if (term.has_bold) + win.message_attr.bold = 1; + else if (term.has_reverse) + win.message_attr.reverse = 1; + else + { + win.message_attr.fg = 0; + win.message_attr.bg = 7; + } + + win.message_attr.is_set = SET; + } + if (!win.search_field_attr.is_set) { win.search_field_attr.bg = 5; @@ -8237,6 +6549,16 @@ win.shift_attr.is_set = SET; } + if (!win.message_attr.is_set) + { + if (term.has_bold) + win.message_attr.bold = 1; + else if (term.has_reverse) + win.message_attr.reverse = 1; + + win.message_attr.is_set = SET; + } + if (!win.search_field_attr.is_set) { if (term.has_reverse) @@ -8335,6 +6657,8 @@ } } + /* Initialize the timeout message when the x/X option is set */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (!quiet_timeout && timeout.initial_value > 0) { switch (timeout.mode) @@ -8356,6 +6680,11 @@ strcat(timeout_message, timeout_word); strcat(timeout_message, "\"]"); break; + + default: + /* The other cases are impossible due to options analysis */ + /* '''''''''''''''''''''''''''''''''''''''''''''''''''''' */ + timeout_message = xstrdup(" "); /* Just in case. */ } timeout_seconds = xcalloc(1, 6); @@ -8419,7 +6748,7 @@ /* Parse the word separators string (option -W). If it is empty then */ /* the standard delimiters (space, tab and EOL) are used. Each of its */ - /* multibyte sequences are stored in a linked list. */ + /* UTF-8 sequences are stored in a linked list. */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ word_delims_list = ll_new(); @@ -8431,26 +6760,26 @@ } else { - int mb_len; + int utf8_len; char * iws_ptr = iws; char * tmp; - mb_len = mblen(iws_ptr, 4); + utf8_len = mblen(iws_ptr, 4); - while (mb_len != 0) + while (utf8_len != 0) { - tmp = xmalloc(mb_len + 1); - memcpy(tmp, iws_ptr, mb_len); - tmp[mb_len] = '\0'; + tmp = xmalloc(utf8_len + 1); + memcpy(tmp, iws_ptr, utf8_len); + tmp[utf8_len] = '\0'; ll_append(word_delims_list, tmp); - iws_ptr += mb_len; - mb_len = mblen(iws_ptr, 4); + iws_ptr += utf8_len; + utf8_len = mblen(iws_ptr, 4); } } /* Parse the line separators string (option -L). If it is empty then */ - /* the standard delimiter (newline) is used. Each of its multibyte */ + /* the standard delimiter (newline) is used. Each of its UTF-8 */ /* sequences are stored in a linked list. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ record_delims_list = ll_new(); @@ -8459,17 +6788,17 @@ ll_append(record_delims_list, "\n"); else { - int mb_len; + int utf8_len; char * ils_ptr = ils; char * tmp; - mb_len = mblen(ils_ptr, 4); + utf8_len = mblen(ils_ptr, 4); - while (mb_len != 0) + while (utf8_len != 0) { - tmp = xmalloc(mb_len + 1); - memcpy(tmp, ils_ptr, mb_len); - tmp[mb_len] = '\0'; + tmp = xmalloc(utf8_len + 1); + memcpy(tmp, ils_ptr, utf8_len); + tmp[utf8_len] = '\0'; ll_append(record_delims_list, tmp); /* Add this record delimiter as a word delimiter */ @@ -8477,8 +6806,8 @@ if (ll_find(word_delims_list, tmp, delims_cmp) == NULL) ll_append(word_delims_list, tmp); - ils_ptr += mb_len; - mb_len = mblen(ils_ptr, 4); + ils_ptr += utf8_len; + utf8_len = mblen(ils_ptr, 4); } } @@ -8498,10 +6827,12 @@ col_index = cols_number = 0; } + /* Compile the regular expression patterns */ + /* """"""""""""""""""""""""""""""""""""""" */ if (daccess_np && regcomp(&daccess_np_re, daccess_np, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", daccess_np); + fprintf(stderr, "Bad regular expression: %s.\n", daccess_np); exit(EXIT_FAILURE); } @@ -8509,7 +6840,7 @@ if (daccess_up && regcomp(&daccess_up_re, daccess_up, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", daccess_up); + fprintf(stderr, "Bad regular expression: %s.\n", daccess_up); exit(EXIT_FAILURE); } @@ -8517,7 +6848,7 @@ if (include_pattern && regcomp(&include_re, include_pattern, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", include_pattern); + fprintf(stderr, "Bad regular expression: %s.\n", include_pattern); exit(EXIT_FAILURE); } @@ -8525,7 +6856,7 @@ if (exclude_pattern && regcomp(&exclude_re, exclude_pattern, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", exclude_pattern); + fprintf(stderr, "Bad regular expression: %s.\n", exclude_pattern); exit(EXIT_FAILURE); } @@ -8534,7 +6865,7 @@ && regcomp(&first_word_re, first_word_pattern, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", first_word_pattern); + fprintf(stderr, "Bad regular expression: %s.\n", first_word_pattern); exit(EXIT_FAILURE); } @@ -8543,7 +6874,7 @@ && regcomp(&last_word_re, last_word_pattern, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", last_word_pattern); + fprintf(stderr, "Bad regular expression: %s.\n", last_word_pattern); exit(EXIT_FAILURE); } @@ -8555,7 +6886,7 @@ REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Bad regular expression %s\n", special_pattern[index]); + fprintf(stderr, "Bad regular expression: %s.\n", special_pattern[index]); exit(EXIT_FAILURE); } @@ -8572,9 +6903,8 @@ { if (!parse_sed_like_string((sed_t *)(node->data))) { - fprintf(stderr, - "Bad -S argument. Must be something like: " - "/regex/repl_string/[g][v][s][i]\n"); + fprintf(stderr, "Bad -S argument. Must be something like: " + "/regex/repl_string/[g][v][s][i].\n"); exit(EXIT_FAILURE); } @@ -8597,9 +6927,8 @@ { if (!parse_sed_like_string((sed_t *)(node->data))) { - fprintf(stderr, - "Bad -I argument. Must be something like: " - "/regex/repl_string/[g][v][s][i]\n"); + fprintf(stderr, "Bad -I argument. Must be something like: " + "/regex/repl_string/[g][v][s][i].\n"); exit(EXIT_FAILURE); } @@ -8619,9 +6948,8 @@ { if (!parse_sed_like_string((sed_t *)(node->data))) { - fprintf(stderr, - "Bad -E argument. Must be something like: " - "/regex/repl_string/[g][v][s][i]\n"); + fprintf(stderr, "Bad -E argument. Must be something like: " + "/regex/repl_string/[g][v][s][i].\n"); exit(EXIT_FAILURE); } @@ -8651,7 +6979,7 @@ if (*unparsed != '\0') { - fprintf(stderr, "Bad -R argument. Unparsed part: %s\n", unparsed); + fprintf(stderr, "Bad -R argument. Unparsed part: %s.\n", unparsed); exit(EXIT_FAILURE); } @@ -8690,7 +7018,7 @@ if (*unparsed != '\0') { - fprintf(stderr, "Bad -C argument. Unparsed part: %s\n", unparsed); + fprintf(stderr, "Bad -C argument. Unparsed part: %s.\n", unparsed); exit(EXIT_FAILURE); } @@ -8798,7 +7126,7 @@ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ while ( (word = get_word(input_file, word_delims_list, record_delims_list, - mb_buffer, &is_last, &toggle, &langinfo, &win, &limits)) + utf8_buffer, &is_last, &toggle, &langinfo, &win, &limits)) != NULL) { int selectable; @@ -9057,8 +7385,7 @@ if (col_index == limits.cols) { fprintf(stderr, - "The number of columns has reached the limit " - "(%ld), exiting.\n", + "The number of columns has reached the limit of %ld.\n", limits.cols); exit(EXIT_FAILURE); @@ -9193,8 +7520,7 @@ if (count == limits.words) { fprintf(stderr, - "The number of read words has reached the limit " - "(%ld), exiting.\n", + "The number of read words has reached the limit of %ld.\n", limits.words); exit(EXIT_FAILURE); @@ -9226,7 +7552,8 @@ /* - Possibly modify the word according to -S/-I/-E arguments */ /* - Replace unprintable characters in the word by mnemonics */ /* - Remember the max size of the words/columns/tabs */ - /* - Insert the word in a TST tree for future search */ + /* - Insert the word in a TST (Ternary Search Tree) index to facilitate */ + /* word search (each node pf the TST will contain an UTF-8 glyph). */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ col_index = 0; for (wi = 0; wi < count; wi++) @@ -9358,17 +7685,32 @@ { if (daccess.size > 0 && daccess.offset + daccess.size + daccess.ignore - <= mb_strlen(word->str)) + <= utf8_strlen(word->str)) { unsigned selector_value; /* numerical value of the * - * extracted selector */ + * extracted selector */ long selector_offset; /* offset in byte to the selector * * to extract */ - char * ptr; /* points just after the selector to extract */ + char * ptr; /* points just after the selector * + * to extract */ + long plus_offset; /* points to the first occurrence * + * of a number in word->str after * + * the offset given */ + + selector_offset = utf8_offset(word->str, daccess.offset); + + if (daccess.plus) + { + plus_offset = strcspn(word->str + selector_offset, + "0123456789"); + + if (plus_offset + daccess.size + daccess.ignore + <= strlen(word->str)) + selector_offset += plus_offset; + } - selector_offset = mb_offset(word->str, daccess.offset); - ptr = word->str + selector_offset; - selector = xstrndup(ptr, daccess.size); + ptr = word->str + selector_offset; + selector = xstrndup(ptr, daccess.size); /* read the embedded number and, if correct, format */ /* it according to daccess.alignment */ @@ -9385,8 +7727,8 @@ /* Overwrite the end of the word to erase the selector */ /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ my_strcpy(ptr, ptr + daccess.size - + mb_offset(ptr + daccess.size, - daccess.ignore)); + + utf8_offset(ptr + daccess.size, + daccess.ignore)); /* Modify the word according to the 'h' directive of -D */ /* """""""""""""""""""""""""""""""""""""""""""""""""""" */ @@ -9412,8 +7754,8 @@ ltrim(selector, " "); rtrim(selector, " ", 0); - tst_daccess = tst_insert(tst_daccess, mb_strtowcs(selector), - word_pos); + tst_daccess = tst_insert(tst_daccess, + utf8_strtowcs(selector), word_pos); if (daccess.follow == 'y') daccess_index = selector_value + 1; @@ -9442,7 +7784,7 @@ /* Insert it in the tst tree containing the selector's */ /* digits. */ /* ''''''''''''''''''''''''''''''''''''''''''''''''''' */ - tst_daccess = tst_insert(tst_daccess, mb_strtowcs(selector), + tst_daccess = tst_insert(tst_daccess, utf8_strtowcs(selector), word_pos); daccess_index++; @@ -9629,7 +7971,7 @@ } s = (long)mbstowcs(NULL, word->str, 0); - s = wcswidth((tmpw = mb_strtowcs(word->str)), s); + s = wcswidth((tmpw = utf8_strtowcs(word->str)), s); free(tmpw); if (s > col_max_size[col_index]) @@ -9657,7 +7999,7 @@ /* """""""""""""""""""""""""""" */ size = (long)mbstowcs(NULL, word->str, 0); - if ((size = wcswidth((tmpw = mb_strtowcs(word->str)), size)) + if ((size = wcswidth((tmpw = utf8_strtowcs(word->str)), size)) > tab_max_size) tab_max_size = size; @@ -9801,7 +8143,7 @@ s1 = (long)strlen(word_a[wi].str); word_width = mbstowcs(NULL, word_a[wi].str, 0); - s2 = wcswidth((w = mb_strtowcs(word_a[wi].str)), word_width); + s2 = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width); free(w); temp = xcalloc(1, col_real_max_size[col_index] + s1 - s2 + 1); memset(temp, ' ', col_max_size[col_index] + s1 - s2); @@ -9835,7 +8177,7 @@ s1 = (long)strlen(word_a[wi].str); word_width = mbstowcs(NULL, word_a[wi].str, 0); - s2 = wcswidth((w = mb_strtowcs(word_a[wi].str)), word_width); + s2 = wcswidth((w = utf8_strtowcs(word_a[wi].str)), word_width); free(w); temp = xcalloc(1, tab_real_max_size + s1 - s2 + 1); memset(temp, ' ', tab_max_size + s1 - s2); @@ -9871,9 +8213,9 @@ /* Note that the direct access selector,if any, is not stored. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (word_a[wi].is_numbered) - w = mb_strtowcs(word_a[wi].str + daccess.flength); + w = utf8_strtowcs(word_a[wi].str + daccess.flength); else - w = mb_strtowcs(word_a[wi].str); + w = utf8_strtowcs(word_a[wi].str); /* If we didn't already encounter this word, then create a new entry in */ /* the TST for it and store its index in its list. */ @@ -9899,8 +8241,8 @@ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ tmp_word = xmalloc(word_real_max_size + 1); - search_data.mb_off_a = xmalloc(word_real_max_size * sizeof(long)); - search_data.mb_len_a = xmalloc(word_real_max_size * sizeof(long)); + search_data.utf8_off_a = xmalloc(word_real_max_size * sizeof(long)); + search_data.utf8_len_a = xmalloc(word_real_max_size * sizeof(long)); win.start = 0; /* index of the first element in the * * words array to be displayed */ @@ -9918,9 +8260,8 @@ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ for (wi = 0; wi < count; wi++) - word_a[wi].bitmap = xcalloc(1, - (word_a[wi].mb - 1 - daccess.flength) / CHAR_BIT - + 1); + word_a[wi].bitmap = xcalloc(1, (word_a[wi].mb - daccess.flength) / CHAR_BIT + + 1); /* Find the first selectable word (if any) in the input stream */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ @@ -9957,7 +8298,7 @@ if (regcomp(&re, pre_selection_index + 1, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Invalid regular expression (%s)\n", pre_selection_index); + fprintf(stderr, "Invalid regular expression :%s.\n", pre_selection_index); exit(EXIT_FAILURE); } @@ -9997,7 +8338,7 @@ ll_t * list; ll_node_t * node; - list = tst_search(tst_word, w = mb_strtowcs(pre_selection_index + 1)); + list = tst_search(tst_word, w = utf8_strtowcs(pre_selection_index + 1)); if (list != NULL) { node = list->head; @@ -10048,7 +8389,7 @@ current = last_selectable; else { - fprintf(stderr, "Invalid index (%s)\n", ptr); + fprintf(stderr, "Invalid index: %s.\n", ptr); exit(EXIT_FAILURE); } @@ -10060,7 +8401,8 @@ wchar_t * w; new_current = last_selectable; - if (NULL != tst_prefix_search(tst_word, w = mb_strtowcs(ptr), tst_cb_cli)) + if (NULL + != tst_prefix_search(tst_word, w = utf8_strtowcs(ptr), tst_cb_cli)) current = new_current; else current = first_selectable; @@ -10077,15 +8419,17 @@ /* We've finished reading from stdin */ /* we will now get the inputs from the controlling terminal if any */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + errno = 0; if (freopen("/dev/tty", "r", stdin) == NULL) - fprintf(stderr, "%s\n", "freopen"); + fprintf(stderr, "/dev/tty: %s\n", strerror(errno)); old_fd1 = dup(1); old_stdout = fdopen(old_fd1, "w"); setbuf(old_stdout, NULL); + errno = 0; if (freopen("/dev/tty", "w", stdout) == NULL) - fprintf(stderr, "%s\n", "freopen"); + fprintf(stderr, "/dev/tty: %s\n", strerror(errno)); setvbuf(stdout, NULL, _IONBF, 0); @@ -10179,6 +8523,32 @@ { int sc = 0; /* scancode */ + /* Manage a segmentation fault by exiting with failure and restoring */ + /* the terminal and the cursor. */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (got_sigsegv) + { + fputs("SIGSEGV received!\n", stderr); + tputs(TPARM1(carriage_return), 1, outch); + tputs(TPARM1(cursor_normal), 1, outch); + restore_term(fileno(stdin)); + + exit(EXIT_FAILURE); + } + + /* Manage the hangup and termination signal by exiting with failure */ + /* and restoring the terminal and the cursor. */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (got_sigterm || got_sighup) + { + fputs("Interrupted!\n", stderr); + tputs(TPARM1(carriage_return), 1, outch); + tputs(TPARM1(cursor_normal), 1, outch); + restore_term(fileno(stdin)); + + exit(EXIT_FAILURE); + } + /* If this alarm is triggered, then redisplay the window */ /* to remove the help message and disable this timer. */ /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ @@ -10460,8 +8830,8 @@ break; case 0x1b: /* ESC */ - /* An escape sequence or a multibyte character has been pressed */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + /* An escape sequence or a UTF-8 sequence has been pressed */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ if (memcmp("\x1bOH", buffer, 3) == 0 || memcmp("\x1bk", buffer, 2) == 0 || memcmp("\x1b[H", buffer, 3) == 0 @@ -10630,6 +9000,18 @@ /* """"""""""""""""""""""""" */ goto kdel; + if (memcmp("\x1b[1;5C", buffer, 6) == 0 + || memcmp("\x1bOc", buffer, 3) == 0) + /* CTRL -> has been pressed */ + /* """"""""""""""""""""""""" */ + goto keol; + + if (memcmp("\x1b[1;5D", buffer, 6) == 0 + || memcmp("\x1bOd", buffer, 3) == 0) + /* CTRL <- key has been pressed */ + /* """""""""""""""""""""""""""" */ + goto ksol; + if (buffer[0] == 0x1b && buffer[1] == '\0') { /* ESC key has been pressed */ @@ -10676,7 +9058,7 @@ case 3: /* ^C */ /* q or Q of ^C has been pressed */ /* """"""""""""""""""""""""""""" */ - if (search_mode != NONE) + if (search_mode != NONE && buffer[0] != 3) goto special_cmds_when_searching; { @@ -10997,7 +9379,7 @@ str = ((output_t *)(node->data))->output_str; fprintf(old_stdout, "%s", str); - width += wcswidth((w = mb_strtowcs(str)), 65535); + width += wcswidth((w = utf8_strtowcs(str)), 65535); free(w); free(str); free(node->data); @@ -11005,7 +9387,7 @@ if (win.sel_sep != NULL) { fprintf(old_stdout, "%s", win.sel_sep); - width += wcswidth((w = mb_strtowcs(win.sel_sep)), 65535); + width += wcswidth((w = utf8_strtowcs(win.sel_sep)), 65535); free(w); } else @@ -11019,7 +9401,7 @@ str = ((output_t *)(node->data))->output_str; fprintf(old_stdout, "%s", str); - width += wcswidth((w = mb_strtowcs(str)), 65535); + width += wcswidth((w = utf8_strtowcs(str)), 65535); free(w); free(str); free(node->data); @@ -11061,7 +9443,7 @@ rtrim(output_str, " \t", 0); } - width = wcswidth((w = mb_strtowcs(output_str)), 65535); + width = wcswidth((w = utf8_strtowcs(output_str)), 65535); free(w); /* And print it. */ @@ -11109,192 +9491,85 @@ exit(EXIT_SUCCESS); } - kl: - /* Cursor Left key has been pressed */ - /* """""""""""""""""""""""""""""""" */ + ksol: + /* Go to the start of the line */ + /* """"""""""""""""""""""""""" */ if (search_mode != NONE) search_mode = NONE; case 'H': - case 'h': if (search_mode == NONE) { - long old_current = current; - long old_start = win.start; - long old_first_column = win.first_column; - - do - { - if (current > 0) - { - if (current == win.start) - if (win.start > 0) - { - for (wi = win.start - 1; wi >= 0 && word_a[wi].start != 0; - wi--) - { - } - win.start = wi; - - if (word_a[wi].str != NULL) - win.start = wi; - - if (win.end < count - 1) - { - for (wi = win.end + 2; - wi < count - 1 && word_a[wi].start != 0; wi++) - { - } - if (word_a[wi].str != NULL) - win.end = wi; - } - } - - /* In column mode we need to take care of the */ - /* horizontal scrolling */ - /* """""""""""""""""""""""""""""""""""""""""" */ - if (win.col_mode || win.line_mode) - { - long pos; + current = first_word_in_line_a[line_nb_of_word_a[current]]; - if (word_a[current].start == 0) - { - long len; + while (!word_a[current].is_selectable) + current++; - len = term.ncolumns - 3; - pos = first_word_in_line_a[line_nb_of_word_a[current - 1]]; + win.first_column = 0; + set_new_first_column(&win, &term); - while (word_a[current - 1].end - win.first_column >= len) - { - win.first_column += word_a[pos].end - word_a[pos].start - + 2; + nl = disp_lines(&win, &toggle, current, count, search_mode, + &search_data, &term, last_line, tmp_word, + &langinfo); + } + else + goto special_cmds_when_searching; - pos++; - } - } - else if (word_a[current - 1].start < win.first_column) - win.first_column = word_a[current - 1].start; - } - current--; - } - else - break; - } while (current != old_current && !word_a[current].is_selectable); + break; - if (!word_a[current].is_selectable) - { - current = old_current; - win.start = old_start; - if (win.col_mode || win.line_mode) - win.first_column = old_first_column; - } + kl: + /* Cursor Left key has been pressed */ + /* """""""""""""""""""""""""""""""" */ + if (search_mode != NONE) + search_mode = NONE; - if (current != old_current) - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - } + case 'h': + if (search_mode == NONE) + move_left(&win, &term, &toggle, &search_data, &langinfo, &nl, + last_line, tmp_word); else goto special_cmds_when_searching; break; - kr: - /* Right key has been pressed */ - /* """""""""""""""""""""""""" */ + keol: + /* Go to the end of the line */ + /* """"""""""""""""""""""""" */ if (search_mode != NONE) search_mode = NONE; case 'L': - case 'l': if (search_mode == NONE) { - long old_current = current; - long old_start = win.start; - long old_first_column = win.first_column; - - do - { - if (current < count - 1) - { - if (current == win.end) - if (win.start < count - 1 && win.end != count - 1) - { - for (wi = win.start + 1; - wi < count - 1 && word_a[wi].start != 0; wi++) - { - } - - if (word_a[wi].str != NULL) - win.start = wi; + long cur_line = line_nb_of_word_a[current]; - if (win.end < count - 1) - { - for (wi = win.end + 2; - wi < count - 1 && word_a[wi].start != 0; wi++) - { - } - if (word_a[wi].str != NULL) - win.end = wi; - } - } + current = get_line_last_word(cur_line, last_line); + win.first_column = win.real_max_width - 1 - (term.ncolumns - 3); - /* In column mode we need to take care of the */ - /* horizontal scrolling */ - /* """""""""""""""""""""""""""""""""""""""""" */ - if (win.col_mode || win.line_mode) - { - if (word_a[current].is_last) - win.first_column = 0; - else - { - long pos; - long len; + while (!word_a[current].is_selectable) + current--; - len = term.ncolumns - 3; + set_new_first_column(&win, &term); - if (word_a[current + 1].end >= len + win.first_column) - { - /* Find the first word to be displayed in this line */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - pos = first_word_in_line_a[line_nb_of_word_a[current]]; - - while (word_a[pos].start <= win.first_column) - pos++; - - pos--; - - /* If the new current word cannot be displayed, search */ - /* the first word in the line that can be displayed by */ - /* iterating on pos. */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - while (word_a[current + 1].end - word_a[pos].start >= len) - pos++; + nl = disp_lines(&win, &toggle, current, count, search_mode, + &search_data, &term, last_line, tmp_word, + &langinfo); + } + else + goto special_cmds_when_searching; - if (word_a[pos].start > 0) - win.first_column = word_a[pos].start; - } - } - } - current++; - } - else - break; - } while (current != old_current && !word_a[current].is_selectable); + break; - if (!word_a[current].is_selectable) - { - current = old_current; - win.start = old_start; - if (win.col_mode || win.line_mode) - win.first_column = old_first_column; - } + kr: + /* Right key has been pressed */ + /* """""""""""""""""""""""""" */ + if (search_mode != NONE) + search_mode = NONE; - if (current != old_current) - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - } + case 'l': + if (search_mode == NONE) + move_right(&win, &term, &toggle, &search_data, &langinfo, &nl, + last_line, tmp_word); else goto special_cmds_when_searching; @@ -11322,140 +9597,8 @@ case 'k': if (search_mode == NONE) - { - long cur_line; - long start_line; - long last_word; - long cursor; - long old_current = current; - long old_start = win.start; - long index; - - /* Store the initial starting and ending positions of */ - /* the word under the cursor */ - /* """""""""""""""""""""""""""""""""""""""""""""""""" */ - s = word_a[current].start; - e = word_a[current].end; - - do - { - /* Identify the line number of the first window's line */ - /* and the line number of the current line */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - start_line = line_nb_of_word_a[win.start]; - cur_line = line_nb_of_word_a[current]; - - if (cur_line == 0) - break; - - /* Manage the different cases */ - /* """""""""""""""""""""""""" */ - if (start_line >= page) - { - if (start_line > cur_line - page) - start_line -= page; - } - else - start_line = 0; - - /* Get the index of the last word of the destination line */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (cur_line >= page) - last_word = first_word_in_line_a[cur_line - page + 1] - 1; - else - last_word = first_word_in_line_a[1] - 1; - - /* And set the new value of the starting word of the window */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - win.start = first_word_in_line_a[start_line]; - - /* Look for the first word whose start position in the line is */ - /* less or equal to the source word starting position */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - cursor = last_word; - while (word_a[cursor].start > s) - cursor--; - - /* In case no word is eligible, keep the cursor on */ - /* the last word */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (cursor == last_word && word_a[cursor].start > 0) - cursor--; - - /* Try to guess the best choice if we have multiple choices */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (word_a[cursor].end >= s - && word_a[cursor].end - s >= e - word_a[cursor + 1].start) - current = cursor; - else - { - if (cursor < last_word) - current = cursor + 1; - else - current = cursor; - } - - /* Set new first column to display */ - /* """"""""""""""""""""""""""""""" */ - set_new_first_column(&win, &term); - - /* If the word is not selectable, try to find a selectable word */ - /* in ts line */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!word_a[current].is_selectable) - { - index = 0; - while (word_a[current - index].start > 0 - && !word_a[current - index].is_selectable) - index++; - - if (word_a[current - index].is_selectable) - current -= index; - else - { - index = 0; - while (current + index < last_word - && !word_a[current + index].is_selectable) - index++; - - if (word_a[current + index].is_selectable) - current += index; - } - } - } while (current != old_current && !word_a[current].is_selectable); - - /* If no selectable word could be find; stay at the original */ - /* position */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!word_a[current].is_selectable) - { - current = old_current; - win.start = old_start; - } - - /* Display the window */ - /* """""""""""""""""" */ - if (current != old_current) - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - else - { - /* We couldn't move to a selectable word, */ - /* try to move the window offset instead */ - /* """""""""""""""""""""""""""""""""""""" */ - if (line_nb_of_word_a[old_start] > 0 - && win.cur_line < win.max_lines && page == 1) - { - win.start = - first_word_in_line_a[line_nb_of_word_a[old_start] - 1]; - - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - } - } - } + move_up(&win, &term, &toggle, &search_data, &langinfo, &nl, page, + first_selectable, last_line, tmp_word); else goto special_cmds_when_searching; @@ -11520,154 +9663,8 @@ case 'j': if (search_mode == NONE) - { - long cur_line; - long start_line; - long last_word; - long cursor; - long old_current = current; - long old_start = win.start; - long index; - - /* Store the initial starting and ending positions of */ - /* the word under the cursor */ - /* """""""""""""""""""""""""""""""""""""""""""""""""" */ - s = word_a[current].start; - e = word_a[current].end; - - do - { - /* Identify the line number of the first window's line */ - /* and the line number of the current line */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""" */ - start_line = line_nb_of_word_a[win.start]; - cur_line = line_nb_of_word_a[current]; - - /* Do nothing when we are already on the last line */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (cur_line == last_line) - break; - - /* Determine and set the future start of the window */ - /* """""""""""""""""""""""""""""""""""""""""""""""" */ - if (start_line > 0 || last_line >= page) - if (cur_line + page > start_line + win.max_lines - 1) - { - if (last_line - (cur_line + page) < page) - { - start_line = last_line - win.max_lines + 1; - win.start = first_word_in_line_a[start_line]; - } - else - { - if (win.end < count - 1) - { - start_line += page; - win.start = first_word_in_line_a[start_line]; - } - } - } - - /* Calculate the index of the last word of the target line */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (cur_line + 1 == last_line) - last_word = count - 1; - else - { - if (cur_line + page < last_line) - last_word = first_word_in_line_a[cur_line + page + 1] - 1; - else - last_word = count - 1; - } - - /* Look for the first word whose start position in the line is */ - /* less or equal than the source word starting position */ - /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - cursor = last_word; - while (word_a[cursor].start > s) - cursor--; - - /* In case no word is eligible, keep the cursor on */ - /* the last word */ - /* """"""""""""""""""""""""""""""""""""""""""""""" */ - if (cursor == last_word && word_a[cursor].start > 0) - cursor--; - - /* Try to guess the best choice if we have multiple choices */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (cursor < count - 1 - && word_a[cursor].end - s >= e - word_a[cursor + 1].start) - current = cursor; - else - { - if (cursor < count - 1) - { - if (cursor < last_word) - current = cursor + 1; - else - current = cursor; - } - else - current = count - 1; - } - - /* Set the new first column to display */ - /* """"""""""""""""""""""""""""""""""" */ - set_new_first_column(&win, &term); - - /* If the word is not selectable, try to find a selectable word */ - /* in ts line */ - /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (!word_a[current].is_selectable) - { - index = 0; - while (word_a[current - index].start > 0 - && !word_a[current - index].is_selectable) - index++; - - if (word_a[current - index].is_selectable) - current -= index; - else - { - index = 0; - while (current + index < last_word - && !word_a[current + index].is_selectable) - index++; - - if (word_a[current + index].is_selectable) - current += index; - } - } - } while (current != old_current && !word_a[current].is_selectable); - - if (!word_a[current].is_selectable) - { - current = old_current; - win.start = old_start; - } - - /* Display the window */ - /* """""""""""""""""" */ - if (current != old_current) - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - else - { - /* We couldn't move to a selectable word, */ - /* try to move the window offset instead */ - /* """""""""""""""""""""""""""""""""""""" */ - if (win.cur_line > 1 && win.end < count - 1 && page == 1) - { - win.start = - first_word_in_line_a[line_nb_of_word_a[old_start] + 1]; - - nl = disp_lines(&win, &toggle, current, count, search_mode, - &search_data, &term, last_line, tmp_word, - &langinfo); - } - } - } + move_down(&win, &term, &toggle, &search_data, &langinfo, &nl, page, + last_selectable, last_line, tmp_word); else goto special_cmds_when_searching; @@ -11943,7 +9940,7 @@ daccess_stack[daccess_stack_head] = buffer[0]; daccess_stack_head++; - w = mb_strtowcs(daccess_stack); + w = utf8_strtowcs(daccess_stack); pos = tst_search(tst_daccess, w); free(w); @@ -12000,24 +9997,24 @@ if (search_mode != NONE) { - if (search_data.mb_len > 0) + if (search_data.utf8_len > 0) { char * prev; - prev = mb_prev(search_data.buf, - search_data.buf + search_data.len - 1); + prev = utf8_prev(search_data.buf, + search_data.buf + search_data.len - 1); - if (search_data.mb_len == search_data.fuzzy_err_pos - 1) + if (search_data.utf8_len == search_data.fuzzy_err_pos - 1) { search_data.fuzzy_err = 0; search_data.fuzzy_err_pos = -1; } - search_data.mb_len--; + search_data.utf8_len--; if (prev) { - *(mb_next(prev)) = '\0'; - search_data.len = prev - search_data.buf + 1; + *(utf8_next(prev)) = '\0'; + search_data.len = prev - search_data.buf + 1; } else { @@ -12031,7 +10028,7 @@ word_a[n].is_matching = 0; memset(word_a[n].bitmap, '\0', - (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1); + (word_a[n].mb - daccess.flength) / CHAR_BIT + 1); } matches_count = 0; @@ -12044,7 +10041,7 @@ else beep(&toggle); - if (search_data.mb_len > 0) + if (search_data.utf8_len > 0) goto special_cmds_when_searching; else /* When there is only one glyph in the search list in */ @@ -12094,8 +10091,8 @@ if (search_mode != NONE) { - long old_len = search_data.len; - long old_mb_len = search_data.mb_len; + long old_len = search_data.len; + long old_utf8_len = search_data.utf8_len; ll_node_t * node; wchar_t * ws; @@ -12108,26 +10105,26 @@ /* mode. As the search buffer has already been amended, */ /* we do not have to update the search buffer again. */ /* '''''''''''''''''''''''''''''''''''''''''''''''''''' */ - for (c = 0; - c < sc - && search_data.mb_len < word_real_max_size - daccess.flength; + for (c = 0; c < sc + && search_data.utf8_len + < word_real_max_size - daccess.flength; c++) search_data.buf[search_data.len++] = buffer[c]; /* Update the glyph array with the content of the search buffer */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ - if (search_data.mb_len < word_real_max_size - daccess.flength) + if (search_data.utf8_len < word_real_max_size - daccess.flength) { - search_data.mb_off_a[search_data.mb_len] = old_len; - search_data.mb_len_a[search_data.mb_len] = search_data.len - - old_len; - search_data.mb_len++; + search_data.utf8_off_a[search_data.utf8_len] = old_len; + search_data.utf8_len_a[search_data.utf8_len] = search_data.len + - old_len; + search_data.utf8_len++; } } if (search_mode == PREFIX) { - ws = mb_strtowcs(search_data.buf); + ws = utf8_strtowcs(search_data.buf); /* Purge the matching words list */ /* """"""""""""""""""""""""""""" */ @@ -12138,7 +10135,7 @@ word_a[n].is_matching = 0; memset(word_a[n].bitmap, '\0', - (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1); + (word_a[n].mb - daccess.flength) / CHAR_BIT + 1); } matches_count = 0; @@ -12177,7 +10174,7 @@ beep(&toggle); search_data.len = old_len; - search_data.mb_len = old_mb_len; + search_data.utf8_len = old_utf8_len; search_data.buf[search_data.len] = '\0'; } } @@ -12189,9 +10186,10 @@ /* level 1 level_2 */ /* */ /* Each sub_tst_t * points to a data structure including */ - /* a sorted array to starting nodes in the words tst. */ + /* a sorted array to nodes in the words tst. */ + /* Each of these node starts a matching candidate */ /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ - wchar_t * w = mb_strtowcs(search_data.buf + old_len); + wchar_t * w = utf8_strtowcs(search_data.buf + old_len); /* zero previous matching indicators */ /* """"""""""""""""""""""""""""""""" */ @@ -12202,7 +10200,7 @@ word_a[n].is_matching = 0; memset(word_a[n].bitmap, '\0', - (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1); + (word_a[n].mb - daccess.flength) / CHAR_BIT + 1); } matches_count = 0; @@ -12222,7 +10220,7 @@ ll_delete(tst_search_list, tst_search_list->tail); } - if (search_data.mb_len == search_data.fuzzy_err_pos - 1) + if (search_data.utf8_len == search_data.fuzzy_err_pos - 1) { search_data.fuzzy_err = 0; search_data.fuzzy_err_pos = -1; @@ -12230,7 +10228,7 @@ } else { - if (search_data.mb_len == 1) + if (search_data.utf8_len == 1) { /* Search all the sub-tst trees having the searched */ /* character as children, the resulting sub-tst are put */ @@ -12245,9 +10243,9 @@ { beep(&toggle); - search_data.len = 0; - search_data.mb_len = 0; - search_data.buf[0] = '\0'; + search_data.len = 0; + search_data.utf8_len = 0; + search_data.buf[0] = '\0'; break; } @@ -12288,12 +10286,12 @@ search_data.fuzzy_err = 1; if (search_data.fuzzy_err_pos == -1) - search_data.fuzzy_err_pos = search_data.mb_len; + search_data.fuzzy_err_pos = search_data.utf8_len; beep(&toggle); search_data.len = old_len; - search_data.mb_len = old_mb_len; + search_data.utf8_len = old_utf8_len; search_data.buf[search_data.len] = '\0'; } } @@ -12344,7 +10342,7 @@ } else /* SUBSTRING */ { - wchar_t * w = mb_strtowcs(search_data.buf); + wchar_t * w = utf8_strtowcs(search_data.buf); /* Purge the matching words list */ /* """"""""""""""""""""""""""""" */ @@ -12355,12 +10353,12 @@ word_a[n].is_matching = 0; memset(word_a[n].bitmap, '\0', - (word_a[n].mb - 1 - daccess.flength) / CHAR_BIT + 1); + (word_a[n].mb - daccess.flength) / CHAR_BIT + 1); } matches_count = 0; - if (search_data.mb_len == 1) + if (search_data.utf8_len == 1) { /* Search all the sub-tst trees having the searched */ /* character as children, the resulting sub-tst are put */ @@ -12424,7 +10422,7 @@ beep(&toggle); search_data.len = old_len; - search_data.mb_len--; + search_data.utf8_len--; search_data.buf[search_data.len] = '\0'; } } diff -Nru smenu-0.9.14/smenu.h smenu-0.9.15/smenu.h --- smenu-0.9.14/smenu.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/smenu.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,598 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef SMENU_H +#define SMENU_H + +#define CHARSCHUNK 8 +#define WORDSCHUNK 8 +#define COLSCHUNK 16 + +#define TPARM1(p, ...) tparm(p, 0, 0, 0, 0, 0, 0, 0, 0, 0) +#define TPARM2(p, q, ...) tparm(p, q, 0, 0, 0, 0, 0, 0, 0, 0) +#define TPARM3(p, q, r, ...) tparm(p, q, r, 0, 0, 0, 0, 0, 0, 0) + +#define _XOPEN_SOURCE 700 + +/* Used for timers management */ +/* """""""""""""""""""""""""" */ +#define SECOND 1000000 +#define FREQ 10 +#define TICK (SECOND / FREQ) + +/* Bit array management */ +/* """""""""""""""""""" */ +#define MASK (CHAR_BIT - 1) +#define SHIFT ((CHAR_BIT == 8) ? 3 : (CHAR_BIT == 16) ? 4 : 8) + +#define BIT_OFF(a, x) ((void)((a)[(x) >> SHIFT] &= ~(1 << ((x)&MASK)))) +#define BIT_ON(a, x) ((void)((a)[(x) >> SHIFT] |= (1 << ((x)&MASK)))) +#define BIT_FLIP(a, x) ((void)((a)[(x) >> SHIFT] ^= (1 << ((x)&MASK)))) +#define BIT_ISSET(a, x) ((a)[(x) >> SHIFT] & (1 << ((x)&MASK))) + +/* ******** */ +/* Typedefs */ +/* ******** */ + +typedef struct charsetinfo_s charsetinfo_t; +typedef struct term_s term_t; +typedef struct toggle_s toggle_t; +typedef struct win_s win_t; +typedef struct word_s word_t; +typedef struct attr_s attr_t; +typedef struct limits_s limits_t; +typedef struct timers_s timers_t; +typedef struct misc_s misc_t; +typedef struct sed_s sed_t; +typedef struct timeout_s timeout_t; +typedef struct output_s output_t; +typedef struct daccess_s daccess_t; +typedef struct search_data_s search_data_t; + +typedef enum filter_types filters_t; +typedef enum daccess_modes da_mode_t; +typedef enum timeout_modes to_mode_t; +typedef enum attribute_settings attr_set_t; +typedef enum search_modes search_mode_t; +typedef enum bitmap_affinities bitmap_affinity_t; + +/* ********** */ +/* Prototypes */ +/* ********** */ + +void +help(win_t * win, term_t * term, long last_line, toggle_t * toggle); + +int +tag_comp(void * a, void * b); + +void +tag_swap(void * a, void * b); + +int +isempty(const char * s); + +void +beep(toggle_t * toggle); + +int +get_cursor_position(int * const r, int * const c); + +void +get_terminal_size(int * const r, int * const c); + +int +#ifdef __sun +outch(char c); +#else +outch(int c); +#endif + +void +restore_term(int const fd); + +void +setup_term(int const fd); + +void +strip_ansi_color(char * s, toggle_t * toggle); + +int +tst_cb(void * elem); + +int +tst_cb_cli(void * elem); + +int +ini_load(const char * filename, win_t * win, term_t * term, limits_t * limits, + timers_t * timers, misc_t * misc, + int (*report)(win_t * win, term_t * term, limits_t * limits, + timers_t * timers, misc_t * misc, const char * section, + const char * name, char * value)); + +int +ini_cb(win_t * win, term_t * term, limits_t * limits, timers_t * timers, + misc_t * misc, const char * section, const char * name, char * value); + +char * +make_ini_path(char * name, char * base); + +void +set_foreground_color(term_t * term, short color); + +void +set_background_color(term_t * term, short color); + +void +set_win_start_end(win_t * win, long current, long last); + +long +build_metadata(term_t * term, long count, win_t * win); + +long +disp_lines(win_t * win, toggle_t * toggle, long current, long count, + search_mode_t search_mode, search_data_t * search_data, + term_t * term, long last_line, char * tmp_word, + langinfo_t * langinfo); + +void +get_message_lines(char * message, ll_t * message_lines_list, + long * message_max_width, long * message_max_len); + +void +disp_message(ll_t * message_lines_list, long width, long max_len, term_t * term, + win_t * win); + +void +update_bitmaps(search_mode_t search_mode, search_data_t * search_data, + bitmap_affinity_t affinity); + +long +find_next_matching_word(long * array, long nb, long value, long * index); + +long +find_prev_matching_word(long * array, long nb, long value, long * index); + +void +clean_matches(search_data_t * search_data, long size); + +void +disp_cursor_word(long pos, win_t * win, term_t * term, int err); + +void +disp_matching_word(long pos, win_t * win, term_t * term, int is_cursor, + int err); + +void +disp_word(long pos, search_mode_t search_mode, search_data_t * search_data, + term_t * term, win_t * win, char * tmp_word); + +size_t +expand(char * src, char * dest, langinfo_t * langinfo, toggle_t * toggle); + +int +get_bytes(FILE * input, char * utf8_buffer, langinfo_t * langinfo); + +int +get_scancode(unsigned char * s, size_t max); + +char * +get_word(FILE * input, ll_t * word_delims_list, ll_t * record_delims_list, + char * utf8_buffer, unsigned char * is_last, toggle_t * toggle, + langinfo_t * langinfo, win_t * win, limits_t * limits); + +void +left_margin_putp(char * s, term_t * term, win_t * win); + +void +right_margin_putp(char * s1, char * s2, langinfo_t * langinfo, term_t * term, + win_t * win, long line, long offset); + +void +sig_handler(int s); + +void +set_new_first_column(win_t * win, term_t * term); + +int +parse_sed_like_string(sed_t * sed); + +void +parse_selectors(char * str, filters_t * filter, char * unparsed, + ll_t ** inc_interval_list, ll_t ** inc_regex_list, + ll_t ** exc_interval_list, ll_t ** exc_regex_list, + langinfo_t * langinfo); + +int +replace(char * orig, sed_t * sed); + +int +decode_attr_toggles(char * s, attr_t * attr); + +int +parse_attr(char * str, attr_t * attr, short max_color); + +void +apply_attr(term_t * term, attr_t attr); + +int +delims_cmp(const void * a, const void * b); + +long +get_line_last_word(long line, long last_line); + +void +move_left(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long last_line, char * tmp_word); + +void +move_right(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long last_line, char * tmp_word); + +int +find_best_word_upward(long last_word, long s, long e); + +int +find_best_word_downward(long last_word, long s, long e); + +void +move_up(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long page, long first_selectable, long last_line, char * tmp_word); + +void +move_down(win_t * win, term_t * term, toggle_t * toggle, + search_data_t * search_data, langinfo_t * langinfo, long * nl, + long page, long last_selectable, long last_line, char * tmp_word); + +/* ***************** */ +/* Emums and structs */ +/* ***************** */ + +/* Various filter pseudo constants */ +/* """"""""""""""""""""""""""""""" */ +enum filter_types +{ + UNKNOWN_FILTER, + INCLUDE_FILTER, + EXCLUDE_FILTER +}; + +/* Used by the -N -F and -D options */ +/* """""""""""""""""""""""""""""""" */ +enum daccess_modes +{ + DA_TYPE_NONE = 0, /* must be 0 (boolean value) */ + DA_TYPE_AUTO = 1, + DA_TYPE_POS = 2 +}; + +/* Used when managing the -R option */ +/* """""""""""""""""""""""""""""""" */ +enum row_regex_types +{ + ROW_REGEX_EXCLUDE = 0, /* must be 0 (boolean value) */ + ROW_REGEX_INCLUDE = 1 +}; + +/* Used when managing the -C option */ +/* """""""""""""""""""""""""""""""" */ +enum filter_infos +{ + EXCLUDE_MARK = 0, /* must be 0 because used in various tests * + * these words cannot be re-included */ + INCLUDE_MARK = 1, /* to forcibly include a word, these words can * + * be excluded later */ + SOFT_EXCLUDE_MARK = 2, /* word with this mark are excluded by default * + * but can be included later */ + SOFT_INCLUDE_MARK = 3 /* word with this mark are included by default * + * but can be excluded later */ +}; + +/* Various timeout mode used by the -x/-X option */ +/* """"""""""""""""""""""""""""""""""""""""""""" */ +enum timeout_modes +{ + CURRENT, /* on timeout, outputs the selected word */ + QUIT, /* on timeout, quit without selecting anything */ + WORD /* on timeout , outputs the specified word */ +}; + +/* Constants used to set the color attributes */ +/* """""""""""""""""""""""""""""""""""""""""" */ +enum attribute_settings +{ + UNSET = 0, /* must be 0 for future testings */ + SET, + FORCED /* an attribute setting has been given in the command line */ +}; + +/* Constant to distinguish between the various search modes */ +/* """""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +enum search_modes +{ + NONE, + PREFIX, + FUZZY, + SUBSTRING +}; + +/* Constants used in search mode to orient the bit-mask building */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +enum bitmap_affinities +{ + NO_AFFINITY, + END_AFFINITY, + START_AFFINITY +}; + +/* Used to store the different allowed charsets data */ +/* """"""""""""""""""""""""""""""""""""""""""""""""" */ +struct charsetinfo_s +{ + char * name; /* canonical name of the allowed charset */ + int bits; /* number of bits in this charset */ +}; + +/* Various toggles which can be set with command line options */ +/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct toggle_s +{ + int del_line; /* 1 if the clean option is set (-d) else 0 */ + int enter_val_in_search; /* 1 if ENTER validates in search mode else 0 */ + int no_scrollbar; /* 1 to disable the scrollbar display else 0 */ + int blank_nonprintable; /* 1 to try to display non-blanks in * + * symbolic form else 0 */ + int keep_spaces; /* 1 to keep the trailing spaces in columns * + * and tabulate mode. */ + int taggable; /* 1 if tagging is enabled */ + int pinable; /* 1 if pinning is selected */ + int autotag; /* 1 if tagging is selected and pinning is * + * not and we do no want an automatic tagging * + * when the users presses */ + int visual_bell; /* 1 to flash the window, 0 to make a sound */ +}; + +/* Structure to store the default or imposed smenu limits */ +/* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct limits_s +{ + long word_length; /* maximum number of bytes in a word */ + long words; /* maximum number of words */ + long cols; /* maximum number of columns */ +}; + +/* Structure to store the default or imposed timers */ +/* """""""""""""""""""""""""""""""""""""""""""""""" */ +struct timers_s +{ + int search; + int help; + int winch; + int direct_access; +}; + +/* Structure to store miscellaneous informations */ +/* """"""""""""""""""""""""""""""""""""""""""""" */ +struct misc_s +{ + search_mode_t default_search_method; +}; + +/* Terminal setting variables */ +/* """""""""""""""""""""""""" */ +struct termios new_in_attrs; +struct termios old_in_attrs; + +/* Interval timers used */ +/* """""""""""""""""""" */ +struct itimerval periodic_itv; /* refresh rate for the timeout counter */ + +int help_timer = -1; +int winch_timer = -1; +int daccess_timer = -1; + +/* Structure containing the attributes components */ +/* """""""""""""""""""""""""""""""""""""""""""""" */ +struct attr_s +{ + attr_set_t is_set; + short fg; + short bg; + signed char bold; + signed char dim; + signed char reverse; + signed char standout; + signed char underline; + signed char italic; +}; + +/* Structure containing some terminal characteristics */ +/* """""""""""""""""""""""""""""""""""""""""""""""""" */ +struct term_s +{ + int ncolumns; /* number of columns */ + int nlines; /* number of lines */ + int curs_column; /* current cursor column */ + int curs_line; /* current cursor line */ + short colors; /* number of available colors */ + short color_method; /* color method (0=classic (0-7), 1=ANSI) */ + + char has_cursor_up; /* has cuu1 terminfo capability */ + char has_cursor_down; /* has cud1 terminfo capability */ + char has_cursor_left; /* has cub1 terminfo capability */ + char has_cursor_right; /* has cuf1 terminfo capability */ + char has_parm_right_cursor; /* has cuf terminfo capability */ + char has_cursor_address; /* has cup terminfo capability */ + char has_save_cursor; /* has sc terminfo capability */ + char has_restore_cursor; /* has rc terminfo capability */ + char has_setf; /* has set_foreground terminfo capability */ + char has_setb; /* has set_background terminfo capability */ + char has_setaf; /* idem for set_a_foreground (ANSI) */ + char has_setab; /* idem for set_a_background (ANSI) */ + char has_hpa; /* has column_address terminfo capability */ + char has_bold; /* has bold mode */ + char has_dim; /* has dim mode */ + char has_reverse; /* has reverse mode */ + char has_underline; /* has underline mode */ + char has_standout; /* has standout mode */ + char has_italic; /* has italic mode */ +}; + +/* Structure describing a word */ +/* """"""""""""""""""""""""""" */ +struct word_s +{ + long start, end; /* start/end absolute horiz. word positions * + * on the screen */ + size_t mb; /* number of UTF-8 glyphs to display */ + long tag_order; /* each time a word is tagged, this value * + * is increased */ + size_t special_level; /* can vary from 0 to 5; 0 meaning normal */ + char * str; /* display string associated with this word */ + size_t len; /* number of bytes of str (for trimming) */ + char * orig; /* NULL or original string if is had been * + * shortened for being displayed or altered * + * by is expansion. */ + char * bitmap; /* used to store the the position of the * + * currently searched chars in a word. The * + * objective is to speed their display */ + unsigned char is_matching; + unsigned char is_tagged; /* 1 if the word is tagged, 0 if not */ + unsigned char is_last; /* 1 if the word is the last of a line */ + unsigned char is_selectable; /* word is selectable */ + unsigned char is_numbered; /* word has a direct access index */ +}; + +/* Structure describing the window in which the user */ +/* will be able to select a word */ +/* """"""""""""""""""""""""""""""""""""""""""""""""" */ +struct win_s +{ + long start, end; /* index of the first and last word */ + long first_column; /* number of the first character displayed */ + long cur_line; /* relative number of the cursor line (1+) */ + long asked_max_lines; /* requested number of lines in the window */ + long max_lines; /* effective number of lines in the window */ + long max_cols; /* max number of words in a single line */ + long real_max_width; /* max line length. In column, tab or line * + * mode it can be greater than the * + * terminal width */ + long message_lines; /* number of lines taken by the messages * + * (updated by disp_message */ + long max_width; /* max usable line width or the terminal */ + long offset; /* window offset user when centered */ + char * sel_sep; /* output separator when tags are enabled */ + char ** gutter_a; /* array of UTF-8 gutter glyphs */ + long gutter_nb; /* number of UTF-8 gutter glyphs */ + + unsigned char tab_mode; /* -t */ + unsigned char col_mode; /* -c */ + unsigned char line_mode; /* -l */ + unsigned char col_sep; /* -g */ + unsigned char wide; /* -w */ + unsigned char center; /* -M */ + + attr_t cursor_attr; /* current cursor attributes */ + attr_t cursor_on_tag_attr; /* current cursor on tag attributes */ + attr_t bar_attr; /* scrollbar attributes */ + attr_t shift_attr; /* shift indicator attributes */ + attr_t message_attr; /* message (title) attributes */ + attr_t search_field_attr; /* search mode field attributes */ + attr_t search_text_attr; /* search mode text attributes */ + attr_t search_err_field_attr; /* bad search mode field attributes */ + attr_t search_err_text_attr; /* bad search mode text attributes */ + attr_t match_field_attr; /* matching word field attributes */ + attr_t match_text_attr; /* matching word text attributes */ + attr_t match_err_field_attr; /* bad matching word field attributes */ + attr_t match_err_text_attr; /* bad matching word text attributes */ + attr_t include_attr; /* selectable words attributes */ + attr_t exclude_attr; /* non-selectable words attributes */ + attr_t tag_attr; /* non-selectable words attributes */ + attr_t daccess_attr; /* direct access tag attributes */ + attr_t special_attr[5]; /* special (-1,...) words attributes */ +}; + +/* Sed like node structure */ +/* """"""""""""""""""""""" */ +struct sed_s +{ + char * pattern; /* pattern to be matched */ + char * substitution; /* substitution string */ + unsigned char visual; /* visual flag: alterations are only * + * visual */ + unsigned char global; /* global flag: alterations can * + * occur more than once */ + unsigned char stop; /* stop flag: only one alteration * + * per word is allowed */ + regex_t re; /* compiled regular expression */ +}; + +/* Structure used to keep track of the different timeout values */ +/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct timeout_s +{ + to_mode_t mode; /* timeout mode: current/quit/word */ + unsigned initial_value; /* 0: no timeout else value in sec */ + unsigned remain; /* remaining seconds */ + unsigned reached; /* 1: timeout has expired, else 0 */ +}; + +/* Structure used during the construction of the pinned words list */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct output_s +{ + long order; /* this field is incremented each time a word is pinned */ + char * output_str; /* The pinned word itself */ +}; + +/* Structure describing the formating of the automatic direct access entries */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct daccess_s +{ + da_mode_t mode; /* DA_TYPE_NONE (0), DA_TYPE_AUTO, DA_TYPE_POS */ + + char * left; /* character to put before the direct access selector */ + char * right; /* character to put after the direct access selector */ + + char alignment; /* l: left; r: right */ + char padding; /* a: all; i: only included words are padded */ + char head; /* What to do with chars before the embedded number */ + int length; /* selector size (5 max) */ + int flength; /* 0 or length + 3 (full prefix lengh */ + size_t offset; /* offset to the start of the selector */ + int plus; /* 1 if we can look for the number to extract after * + * the offset, else 0. (a '+' follows the offset) */ + int size; /* size in bytes of the selector to extract */ + size_t ignore; /* number of UTF-8 glyphs to ignore after the number */ + char follow; /* y: the numbering follows the last nuber set */ + char * num_sep; /* character to separate de number and the selection */ + int def_number; /* 1: the numbering is on by default 0: it is not */ +}; + +/* Structure used in search mod to store the current buffer and various */ +/* related values. */ +/* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct search_data_s +{ + char * buf; /* search buffer */ + long len; /* current position in the search buffer */ + long utf8_len; /* current position in the search buffer in * + * UTF-8 units */ + long * utf8_off_a; /* array of mb offsets in buf */ + long * utf8_len_a; /* array of mb lengths in buf */ + + int fuzzy_err; /* fuzzy match error indicator */ + long fuzzy_err_pos; /* last good position in search buffer */ + + int only_ending; /* only searches giving a result with the * + * pattern at the end of the word will be * + * selected */ + int only_starting; /* same with the pattern at the beginning */ +}; + +#endif diff -Nru smenu-0.9.14/smenu.spec.in smenu-0.9.15/smenu.spec.in --- smenu-0.9.14/smenu.spec.in 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/smenu.spec.in 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,7 @@ # # spec file for package smenu # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,7 +12,7 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # @@ -20,25 +20,17 @@ Version: @VERSION@ Release: 0 Summary: A standard input word picker -License: GPL-2.0 +License: GPL-2.0-only Group: Productivity/Text/Utilities -Url: https://github.com/p-gen/smenu +URL: https://github.com/p-gen/%{name} Source: %{name}-%{version}.tar.bz2 BuildRequires: ncurses-devel BuildRoot: %{_tmppath}/%{name}-%{version}-build %description -This simple tool reads words from a file or the standard input, presents -them in a cool interactive window after the current line on the terminal -and writes the selected word, if any, on the standard output. - -After having unsuccessfully searched the NET for what I wanted, I -decided to try to write my own. - -I have tried hard to made its usage as simple as possible. It should -work, even when using an old "vt100" terminal and is "UTF-8" aware. - -Author: Pierre Gentile +This tool reads words from a file or standard input, presents them in an +interactive window after the current line on the terminal, and writes the +selected words, if any, to standard output. %prep %setup -q @@ -48,35 +40,33 @@ make %{?_smp_mflags} %package tests -Summary: %{name} testing system -Group: Productivity/Text/Utilities +Summary: Testing system for %{name} +Group: Productivity/Text/Utilities %description tests -This packages contains some scripts and a bunch of test to check the +This packages contains some scripts and a number of tests to check the %{name} tool. %install -make DESTDIR=%{buildroot} install %{?_smp_mflags} -install -d %{buildroot}%{_defaultdocdir}/%{name} -for FILE in COPYRIGHT *.rst examples tests; do - mv ${FILE} %{buildroot}%{_defaultdocdir}/%{name} -done +%if 0%{?suse_version} < 1315 +make install DESTDIR="%{?buildroot}" +%else +%make_install +%endif %files tests %defattr(-,root,root,-) -%dir %{_defaultdocdir}/%{name}/tests -%doc %{_defaultdocdir}/%{name}/tests/README.rst -%doc %{_defaultdocdir}/%{name}/tests/test.sh -%doc %{_defaultdocdir}/%{name}/tests/tests.sh -%doc %{_defaultdocdir}/%{name}/tests/tests.cpio.gz +%doc tests %files %defattr(-,root,root,-) %attr(0755,root,root) %{_bindir}/* -%dir %{_defaultdocdir}/%{name} -%doc %{_defaultdocdir}/%{name}/examples -%doc %{_defaultdocdir}/%{name}/COPYRIGHT -%doc %{_defaultdocdir}/%{name}/*.rst +%if 0%{?sle_version} < 120300 +%doc COPYRIGHT +%else +%license COPYRIGHT +%endif +%doc examples README.rst FAQ %{_mandir}/man1/* %changelog diff -Nru smenu-0.9.14/tests/direct_access/data7 smenu-0.9.15/tests/direct_access/data7 --- smenu-0.9.14/tests/direct_access/data7 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/direct_access/data7 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +"01 item 1" +" 10 item 10" +" 11 item 11" +"02 item 1" +" 20 item 20" +" 21 item 21" diff -Nru smenu-0.9.14/tests/direct_access/t0018.good smenu-0.9.15/tests/direct_access/t0018.good --- smenu-0.9.14/tests/direct_access/t0018.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/direct_access/t0018.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(smenu -l -F -D o:0+ i:1 n:2 -- t0018.in) + + 1) item 1 ┐ +0:01 1:01 2:01 3:01 16:20 + 10) item 10 │ +0:01 1:01 2:01 3:01 16:20 + 11) item 11 ║ +0:01 1:01 2:01 3:01 5:07 6:07 7:07 8:07 9:07 10:07 11:07 12:07 13:07 16:20 + 2) item 1 │ +0:01 1:01 2:01 3:01 16:20 + 20) item 20 ▼ +0:01 1:01 2:01 3:01 16:20 +$ + +$ echo ":$OUT:" + +:item 11: + +$ exit 0 diff -Nru smenu-0.9.14/tests/direct_access/t0018.in smenu-0.9.15/tests/direct_access/t0018.in --- smenu-0.9.14/tests/direct_access/t0018.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/direct_access/t0018.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +"01 item 1" +" 10 item 10" +" 11 item 11" +"02 item 1" +" 20 item 20" +" 21 item 21" diff -Nru smenu-0.9.14/tests/direct_access/t0018.tst smenu-0.9.15/tests/direct_access/t0018.tst --- smenu-0.9.14/tests/direct_access/t0018.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/direct_access/t0018.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(smenu -l -F -D o:0+ i:1 n:2 -- t0018.in) +\S[150]\s[150]jj\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/message/t0001.good smenu-0.9.15/tests/message/t0001.good --- smenu-0.9.14/tests/message/t0001.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0001.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,9 @@ $ OUT=$(smenu -m xx -n 10 -M t0001.in) xx -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 +37:01 38:01 + + 1 2 3 4 5 6 7 8 9 10 36:07 $ diff -Nru smenu-0.9.14/tests/message/t0002.good smenu-0.9.15/tests/message/t0002.good --- smenu-0.9.14/tests/message/t0002.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0002.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ test 0:01 1:01 2:01 3:01 + + 1 2 3 4 5 6 7 8 9 10 0:07 $ diff -Nru smenu-0.9.14/tests/message/t0003.good smenu-0.9.15/tests/message/t0003.good --- smenu-0.9.14/tests/message/t0003.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0003.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,9 @@ $ OUT=$(smenu -M -l -m test t0003.in) test -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 +36:01 37:01 38:01 39:01 + + 123456789 123456789 123456789 123456789 123456789 123456789 123456789→ 0:01 1:07 2:07 3:07 4:07 5:07 6:07 7:07 8:07 9:07 70:01 $ diff -Nru smenu-0.9.14/tests/message/t0004.good smenu-0.9.15/tests/message/t0004.good --- smenu-0.9.14/tests/message/t0004.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0004.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,10 +2,12 @@ > yyyy' t0004.in) - xx -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 + xx +36:01 37:01 38:01 39:01 yyyy -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 +36:01 37:01 38:01 39:01 + + 1 2 3 4 5 6 7 8 9 10 28:07 $ diff -Nru smenu-0.9.14/tests/message/t0005.good smenu-0.9.15/tests/message/t0005.good --- smenu-0.9.14/tests/message/t0005.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0005.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,9 @@ $ OUT=$(smenu -M -m yyyy t0005.in) yyyy -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 +36:01 37:01 38:01 39:01 + + 1 2 3 4 5 6 7 8 9 10 28:07 $ diff -Nru smenu-0.9.14/tests/message/t0006.good smenu-0.9.15/tests/message/t0006.good --- smenu-0.9.14/tests/message/t0006.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0006.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,9 +1,11 @@ -$ OUT=$(smenu -M -m "123456789 123456789 123456789 123456789 \ +$ OUT=$(smenu -a m:r -M -m "123456789 123456789 123456789 123456789 \ > 123456789 123456789 123456789 123456789 123456789" t0006.in) 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 51:01 52:01 53:01 54:01 55:01 56:01 57:01 58:01 59:01 60:01 61:01 62:01 63:01 64:01 65:01 66:01 67:01 68:01 69:01 70:01 71:01 72:01 73:01 74:01 75:01 76:01 77:01 78:01 79:01 +0:07 1:07 2:07 3:07 4:07 5:07 6:07 7:07 8:07 9:07 10:07 11:07 12:07 13:07 14:07 15:07 16:07 17:07 18:07 19:07 20:07 21:07 22:07 23:07 24:07 25:07 26:07 27:07 28:07 29:07 30:07 31:07 32:07 33:07 34:07 35:07 36:07 37:07 38:07 39:07 40:07 41:07 42:07 43:07 44:07 45:07 46:07 47:07 48:07 49:07 50:07 51:07 52:07 53:07 54:07 55:07 56:07 57:07 58:07 59:07 60:07 61:07 62:07 63:07 64:07 65:07 66:07 67:07 68:07 69:07 70:07 71:07 72:07 73:07 74:07 75:07 76:07 77:07 78:07 79:07 + + 1 2 3 4 5 6 7 8 9 10 28:07 $ diff -Nru smenu-0.9.14/tests/message/t0006.tst smenu-0.9.15/tests/message/t0006.tst --- smenu-0.9.14/tests/message/t0006.tst 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0006.tst 2019-03-30 14:08:14.000000000 +0000 @@ -1,4 +1,4 @@ -\S[150]\s[10]OUT=$(smenu -M -m "123456789 123456789 123456789 123456789 \\ +\S[150]\s[10]OUT=$(smenu -a m:r -M -m "123456789 123456789 123456789 123456789 \\ 123456789 123456789 123456789 123456789 123456789" t0006.in) \S[150]\s[150]\r \S[150]\s[10]echo ":$\s[10]OUT:" diff -Nru smenu-0.9.14/tests/message/t0007.good smenu-0.9.15/tests/message/t0007.good --- smenu-0.9.14/tests/message/t0007.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/message/t0007.good 2019-03-30 14:08:14.000000000 +0000 @@ -4,6 +4,8 @@ 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 51:01 52:01 53:01 54:01 55:01 56:01 57:01 58:01 59:01 60:01 61:01 62:01 63:01 64:01 65:01 66:01 67:01 68:01 69:01 70:01 71:01 72:01 73:01 74:01 75:01 76:01 77:01 78:01 79:01 + + 1 2 3 4 5 6 7 8 9 10 0:07 $ diff -Nru smenu-0.9.14/tests/message/t0008.good smenu-0.9.15/tests/message/t0008.good --- smenu-0.9.14/tests/message/t0008.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/message/t0008.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,21 @@ +$ OUT=$(smenu -a m:7/5 -M \ + +> -m '=\uefbcb4\uefbca5\uefbcb3\uefbcb4= + +> =test=' t0008.in) + + =TEST= +33:252d 34:252d 35:252d 36:252d 37:252d 38:252d + =test= +33:252d 34:252d 35:252d 36:252d 37:252d 38:252d 39:252d 40:252d 41:252d 42:252d + + + 1 2 3 4 5 6 7 8 9 10 +28:07 +$ + +$ echo ":$OUT:" + +:1: + +$ exit 0 diff -Nru smenu-0.9.14/tests/message/t0008.in smenu-0.9.15/tests/message/t0008.in --- smenu-0.9.14/tests/message/t0008.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/message/t0008.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff -Nru smenu-0.9.14/tests/message/t0008.tst smenu-0.9.15/tests/message/t0008.tst --- smenu-0.9.14/tests/message/t0008.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/message/t0008.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,5 @@ +\S[150]\s[10]OUT=$(smenu -a m:7/5 -M \\ +-m '=\\uefbcb4\\uefbca5\\uefbcb3\\uefbcb4=\n=test=' t0008.in) +\S[150]\s[150]\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/README.rst smenu-0.9.15/tests/README.rst --- smenu-0.9.14/tests/README.rst 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/README.rst 2019-03-30 14:08:14.000000000 +0000 @@ -44,6 +44,7 @@ - Decompress the **tests.cpio.gz** archive (``gzip -dv tests.cpio.gz``). - Extract the tests from the **tests.cpio** archive in this directory (``cpio -idv < tests.cpio``). + - Make tests.sh and test.sh executable (``chmod u+x tests.sh test.sh``) - Make sure that your terminal has at least a size of 80x84. - Make sure that you have previously built **smenu** with ``build.sh``. @@ -72,6 +73,6 @@ Note ---- -Before reporting a bug due to a failing test, please re-execute is again -at least 3 times with ``test.sh`` as you may have found a i(timing) +Before reporting a bug due to a failing test, please re-execute is +again at least 3 times with ``test.sh`` as you may have found a (timing) bug in **ptylie**/**hlvt** and not in **smenu**. diff -Nru smenu-0.9.14/tests/scrolling/data2 smenu-0.9.15/tests/scrolling/data2 --- smenu-0.9.14/tests/scrolling/data2 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data2 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,45 @@ +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +b b b b b +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b b b b +b b a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +a a a a a +b b b b b diff -Nru smenu-0.9.14/tests/scrolling/data3 smenu-0.9.15/tests/scrolling/data3 --- smenu-0.9.14/tests/scrolling/data3 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data3 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/data4 smenu-0.9.15/tests/scrolling/data4 --- smenu-0.9.14/tests/scrolling/data4 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data4 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,16 @@ + 0 a a a a a + 1 a a a a a + 2 a a a a a + 3 b b b a a + 4 a a a a a + 5 b b b b b + 6 b b b b b + 7 b b b b b + 8 b b b b b + 9 a a a a a + 10 b b b b b + 11 a a a a a + 12 a a a a a + 13 a a a a a + 14 a a a a a + 15 a a a a a diff -Nru smenu-0.9.14/tests/scrolling/data5 smenu-0.9.15/tests/scrolling/data5 --- smenu-0.9.14/tests/scrolling/data5 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data5 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,12 @@ + 0 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 1 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 2 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 3 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 4 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 5 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 6 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 7 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 8 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 9 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 10 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 + 11 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 diff -Nru smenu-0.9.14/tests/scrolling/data6 smenu-0.9.15/tests/scrolling/data6 --- smenu-0.9.14/tests/scrolling/data6 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data6 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,14 @@ + 0 abcdefghi xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx + 1 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija + 2 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 3 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 4 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 5 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 6 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 7 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija + 8 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 9 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 10 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 11 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 12 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 13 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija diff -Nru smenu-0.9.14/tests/scrolling/data7 smenu-0.9.15/tests/scrolling/data7 --- smenu-0.9.14/tests/scrolling/data7 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/data7 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,20 @@ + 0 a a a a a + 1 a a a a a + 2 a a a a a + 3 a a a a a + 4 a a a a a + 5 a a a a a + 6 b b b a a + 7 a a a a a + 8 b b b b b + 9 b b b b b + 10 b b b b b + 11 b b b b b + 12 a a a a a + 13 b b b b b + 14 a a a a a + 15 a a a a a + 16 a a a a a + 17 a a a a a + 18 a a a a a + 19 a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0003.good smenu-0.9.15/tests/scrolling/t0003.good --- smenu-0.9.14/tests/scrolling/t0003.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0003.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,17 @@ +$ OUT=$(LC_ALL=en_US smenu -n 4 -l t0002.in) + +1 2 3 4 5 \ +6:07 16:20 +6 7 8 9 10 + +16:20 +11 12 13 14 15 | +16:20 +16 17 18 19 20 v +16:20 +$ + +$ echo ":$OUT:" + +:4: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0003.in smenu-0.9.15/tests/scrolling/t0003.in --- smenu-0.9.14/tests/scrolling/t0003.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0003.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,45 @@ +0 a a a a +1 a a a a +2 a a a a +3 a a a a +4 a a a a +5 b b b b +6 a a a a +7 a a a a +8 a a a a +9 a a a a +10 a a a a +11 a a a a +12 a a a a +13 a a a a +14 a a a a +15 a a a a +16 a a a a +17 a a a a +18 a a a a +19 a a b b +20 b b b b +21 b b b b +22 b b b b +23 b b b b +24 b b b b +25 b b b b +26 b b b b +27 b b b b +28 b b b b +29 b b b b +30 b b b b +31 b b b b +32 b b a a +33 a a a a +34 a a a a +35 a a a a +36 a a a a +37 a a a a +38 a a a a +39 a a a a +40 a a a a +41 a a a a +42 a a a a +43 a a a a +44 b b b b diff -Nru smenu-0.9.14/tests/scrolling/t0003.tst smenu-0.9.15/tests/scrolling/t0003.tst --- smenu-0.9.14/tests/scrolling/t0003.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0003.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -n 4 -l t0002.in) +\S[150]\s[150]lljjlljjhhjjhhkkhhkkhhjjllkk\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0004.good smenu-0.9.15/tests/scrolling/t0004.good --- smenu-0.9.14/tests/scrolling/t0004.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0004.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -e [0-9] -e a -c t0004.in) + +13 b b b b b b b b ^ +0:24 1:24 9:07 20:20 +14 b b b b b b b b | +0:24 1:24 20:20 +15 b b b b a a a a + +0:24 1:24 11:24 13:24 15:24 17:24 20:20 +16 a a a a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +17 a a a a a a a a v +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +$ + +$ echo ":$OUT:" + +:b: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0004.in smenu-0.9.15/tests/scrolling/t0004.in --- smenu-0.9.14/tests/scrolling/t0004.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0004.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0004.tst smenu-0.9.15/tests/scrolling/t0004.tst --- smenu-0.9.14/tests/scrolling/t0004.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0004.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -e [0-9] -e a -c t0004.in) +\S[150]\s[150]llkkkkjjjjjjjjjjkk\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0005.good smenu-0.9.15/tests/scrolling/t0005.good --- smenu-0.9.14/tests/scrolling/t0005.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0005.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -R 7,13 -c t0005.in) + +11 a a a a a ^ +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +12 a a a a a | +5:07 14:20 +13 a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +14 a a a a a + +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +15 a a a a a / +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +$ + +$ echo ":$OUT:" + +:a: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0005.in smenu-0.9.15/tests/scrolling/t0005.in --- smenu-0.9.14/tests/scrolling/t0005.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0005.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,16 @@ + 0 a a a a a + 1 a a a a a + 2 a a a a a + 3 b b b a a + 4 a a a a a + 5 b b b b b + 6 b b b b b + 7 b b b b b + 8 b b b b b + 9 a a a a a + 10 b b b b b + 11 a a a a a + 12 a a a a a + 13 a a a a a + 14 a a a a a + 15 a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0005.tst smenu-0.9.15/tests/scrolling/t0005.tst --- smenu-0.9.14/tests/scrolling/t0005.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0005.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -R 7,13 -c t0005.in) +\S[150]\s[150]lljjjjjjjkkkkkkkjjjKKJJKJ\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0006.good smenu-0.9.15/tests/scrolling/t0006.good --- smenu-0.9.14/tests/scrolling/t0006.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0006.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -R 6,18,20 -c t0006.in) + +3 a a a a a a a a ^ +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +4 a a a a a a a a + +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +5 a a a a a a a a | +5:07 20:20 +6 a a a a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +7 a a a a a a a a v +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +$ + +$ echo ":$OUT:" + +:a: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0006.in smenu-0.9.15/tests/scrolling/t0006.in --- smenu-0.9.14/tests/scrolling/t0006.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0006.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0006.tst smenu-0.9.15/tests/scrolling/t0006.tst --- smenu-0.9.14/tests/scrolling/t0006.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0006.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -R 6,18,20 -c t0006.in) +\S[150]\s[150]lljjjjjjjkkkkkkkjjjKKKJJJKJKJjjjkkkJK\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0007.good smenu-0.9.15/tests/scrolling/t0007.good --- smenu-0.9.14/tests/scrolling/t0007.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0007.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -R 4,6,11,16 -c t0007.in) + +5 a a a a a a a a ^ +5:07 20:20 +6 a a a a a a a a + +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +7 a a a a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +8 a a a a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +9 a a a a a a a a v +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +$ + +$ echo ":$OUT:" + +:a: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0007.in smenu-0.9.15/tests/scrolling/t0007.in --- smenu-0.9.14/tests/scrolling/t0007.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0007.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0007.tst smenu-0.9.15/tests/scrolling/t0007.tst --- smenu-0.9.14/tests/scrolling/t0007.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0007.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -R 4,6,11,16 -c t0007.in) +\S[150]\s[150]lljjjjjjjkkkkkkkjjjKKKJJJKJKJjjjkkkJK\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0008.good smenu-0.9.15/tests/scrolling/t0008.good --- smenu-0.9.14/tests/scrolling/t0008.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0008.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -c -e x -e '[0-9]' t0008.in) + + \ +0:01 1:07 2:07 3:07 4:07 5:07 6:07 7:07 8:07 9:07 11:24 12:24 13:24 14:24 15:24 16:24 17:24 18:24 19:24 21:24 22:24 23:24 24:24 25:24 26:24 27:24 28:24 29:24 31:24 32:24 33:24 34:24 35:24 36:24 37:24 38:24 39:24 41:24 42:24 43:24 44:24 45:24 46:24 47:24 48:24 49:24 51:24 52:24 53:24 54:24 55:24 56:24 57:24 58:24 59:24 61:24 62:24 63:24 64:24 65:24 66:24 67:24 68:24 69:24 70:01 79:20 + + +0:01 1:24 2:24 3:24 4:24 5:24 6:24 7:24 8:24 9:24 11:24 12:24 13:24 14:24 15:24 16:24 17:24 18:24 19:24 21:24 22:24 23:24 24:24 25:24 26:24 27:24 28:24 29:24 31:24 32:24 33:24 34:24 35:24 36:24 37:24 38:24 39:24 41:24 42:24 43:24 44:24 45:24 46:24 47:24 48:24 49:24 51:24 52:24 53:24 54:24 55:24 56:24 57:24 58:24 59:24 61:24 62:24 63:24 64:24 65:24 66:24 67:24 68:24 69:24 70:01 79:20 + | +0:01 70:01 79:20 + | +0:01 70:01 79:20 + v +0:01 70:01 79:20 +$ + +$ echo ":$OUT:" + +:abcdefghi: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0008.in smenu-0.9.15/tests/scrolling/t0008.in --- smenu-0.9.14/tests/scrolling/t0008.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0008.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,14 @@ + 0 abcdefghi xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx + 1 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija + 2 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 3 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 4 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 5 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 6 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 7 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija + 8 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 9 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 10 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 11 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 12 abcdefghi jabcdefgh ijabcdefg hijbcdefg hijabcdef ghijabcde fghijabcd efghijabc defghija + 13 xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxx defghija diff -Nru smenu-0.9.14/tests/scrolling/t0008.tst smenu-0.9.15/tests/scrolling/t0008.tst --- smenu-0.9.14/tests/scrolling/t0008.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0008.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -c -e x -e '[0-9]' t0008.in) +\S[150]\s[150]JJJKKK\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0009.good smenu-0.9.15/tests/scrolling/t0009.good --- smenu-0.9.14/tests/scrolling/t0009.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0009.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -c -Re1,22 -e '[0-9]' t0009.in) + +0 a a a a a a a a \ +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +1 a a a a a a a a + +0:24 1:24 3:07 20:20 +2 a a a a a a a a | +0:24 1:24 20:20 +3 a a a a a a a a | +0:24 1:24 20:20 +4 a a a a a a a a v +0:24 1:24 20:20 +$ + +$ echo ":$OUT:" + +:a: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0009.in smenu-0.9.15/tests/scrolling/t0009.in --- smenu-0.9.14/tests/scrolling/t0009.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0009.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0009.tst smenu-0.9.15/tests/scrolling/t0009.tst --- smenu-0.9.14/tests/scrolling/t0009.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0009.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -c -Re1,22 -e '[0-9]' t0009.in) +\S[150]\s[150]jjjjkkkkkJjKK\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0010.good smenu-0.9.15/tests/scrolling/t0010.good --- smenu-0.9.14/tests/scrolling/t0010.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0010.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -c -Re1,22 -e '[0-9]' t0010.in) + +17 a a a a a a a a ^ +0:24 1:24 20:20 +18 a a a a a a a a | +0:24 1:24 20:20 +19 a a a a a a a a | +0:24 1:24 20:20 +20 a a a a a a a a + +0:24 1:24 17:07 20:20 +21 a a a a a a a a / +0:24 1:24 3:24 5:24 7:24 9:24 11:24 13:24 15:24 17:24 20:20 +$ + +$ echo ":$OUT:" + +:a: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0010.in smenu-0.9.15/tests/scrolling/t0010.in --- smenu-0.9.14/tests/scrolling/t0010.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0010.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,22 @@ + 0 a a a a a a a a + 1 a a a a a a a a + 2 a a a a a a a a + 3 a a a a a a a a + 4 a a a a a a a a + 5 a a a a a a a a + 6 a a a a a a a a + 7 a a a a a a a a + 8 a a a a a a a a + 9 a a a a a a a a + 10 a a a a a a a a + 11 a a a a b b b b + 12 b b b b b b b b + 13 b b b b b b b b + 14 b b b b b b b b + 15 b b b b a a a a + 16 a a a a a a a a + 17 a a a a a a a a + 18 a a a a a a a a + 19 a a a a a a a a + 20 a a a a a a a a + 21 a a a a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0010.tst smenu-0.9.15/tests/scrolling/t0010.tst --- smenu-0.9.14/tests/scrolling/t0010.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0010.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -c -Re1,22 -e '[0-9]' t0010.in) +\S[150]\s[150]\CJkkkkjjjjjKkJJ\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0011.good smenu-0.9.15/tests/scrolling/t0011.good --- smenu-0.9.14/tests/scrolling/t0011.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0011.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +$ OUT=$(LC_ALL=en_US smenu -c -e a -e '[0-9]' t0011.in) + +2 a a a a a ^ +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +3 a a a a a + +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +4 a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +5 a a a a a | +0:24 1:24 3:24 5:24 7:24 9:24 11:24 14:20 +6 b b b a a v +0:24 1:24 3:07 9:24 11:24 14:20 +$ + +$ echo ":$OUT:" + +:b: + +$ exit 0 diff -Nru smenu-0.9.14/tests/scrolling/t0011.in smenu-0.9.15/tests/scrolling/t0011.in --- smenu-0.9.14/tests/scrolling/t0011.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0011.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,20 @@ + 0 a a a a a + 1 a a a a a + 2 a a a a a + 3 a a a a a + 4 a a a a a + 5 a a a a a + 6 b b b a a + 7 a a a a a + 8 b b b b b + 9 b b b b b + 10 b b b b b + 11 b b b b b + 12 a a a a a + 13 b b b b b + 14 a a a a a + 15 a a a a a + 16 a a a a a + 17 a a a a a + 18 a a a a a + 19 a a a a a diff -Nru smenu-0.9.14/tests/scrolling/t0011.tst smenu-0.9.15/tests/scrolling/t0011.tst --- smenu-0.9.14/tests/scrolling/t0011.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/scrolling/t0011.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(LC_ALL=en_US smenu -c -e a -e '[0-9]' t0011.in) +\S[150]\s[150]JJJJKKKKK\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/search/data5 smenu-0.9.15/tests/search/data5 --- smenu-0.9.14/tests/search/data5 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/data5 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +ab +ababab +abxab +" abab" +"abab " +" abab " diff -Nru smenu-0.9.14/tests/search/t0026.good smenu-0.9.15/tests/search/t0026.good --- smenu-0.9.14/tests/search/t0026.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0026.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,11 @@ +$ OUT=$(smenu -n 0 -N -- t0026.in) + + 1) ab 2) ababab 3) abxab 4) abab 5) abab 6) abab +0:01 1:01 2:01 4:0723 5:0723 7:01 8:01 9:01 15:23 16:23 18:01 19:01 20:01 25:23 26:23 28:01 29:01 30:01 35:23 36:23 38:01 39:01 40:01 48:01 49:01 50:01 +$ + +$ echo ":$OUT:" + +:ab: + +$ exit 0 diff -Nru smenu-0.9.14/tests/search/t0026.in smenu-0.9.15/tests/search/t0026.in --- smenu-0.9.14/tests/search/t0026.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0026.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +ab +ababab +abxab +" abab" +"abab " +" abab " diff -Nru smenu-0.9.14/tests/search/t0026.tst smenu-0.9.15/tests/search/t0026.tst --- smenu-0.9.14/tests/search/t0026.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0026.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(smenu -n 0 -N -- t0026.in) +\S[150]\s[150]~ab\CZ\rj\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/search/t0027.good smenu-0.9.15/tests/search/t0027.good --- smenu-0.9.14/tests/search/t0027.good 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0027.good 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,21 @@ +$ OUT=$(smenu -n 0 -N -c t0027.in) + + 1) ab +0:01 1:01 2:01 4:23 5:23 + 2) ababab +0:01 1:01 2:01 4:07 5:07 6:07 7:07 8:0723 9:0723 + 3) abxab +0:01 1:01 2:01 7:23 8:23 + 4) abab +0:01 1:01 2:01 7:23 8:23 + 5) abab +0:01 1:01 2:01 + 6) abab +0:01 1:01 2:01 +$ + +$ echo ":$OUT:" + +:ababab: + +$ exit 0 diff -Nru smenu-0.9.14/tests/search/t0027.in smenu-0.9.15/tests/search/t0027.in --- smenu-0.9.14/tests/search/t0027.in 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0027.in 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,6 @@ +ab +ababab +abxab +" abab" +"abab " +" abab " diff -Nru smenu-0.9.14/tests/search/t0027.tst smenu-0.9.15/tests/search/t0027.tst --- smenu-0.9.14/tests/search/t0027.tst 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/tests/search/t0027.tst 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,4 @@ +\S[150]\s[10]OUT=$(smenu -n 0 -N -c t0027.in) +\S[150]\s[150]~ab\CZ\rj\r +\S[150]\s[10]echo ":$\s[10]OUT:" +exit 0 diff -Nru smenu-0.9.14/tests/test.sh smenu-0.9.15/tests/test.sh --- smenu-0.9.14/tests/test.sh 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/test.sh 2019-03-30 14:08:14.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Find backwards for the directory where test.sh is # """"""""""""""""""""""""""""""""""""""""""""""""" diff -Nru smenu-0.9.14/tests/tests.sh smenu-0.9.15/tests/tests.sh --- smenu-0.9.14/tests/tests.sh 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/tests.sh 2019-03-30 14:08:14.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # =========================================================================== # Usage: ./tests.sh [test_directory] diff -Nru smenu-0.9.14/tests/timeout/t0001.good smenu-0.9.15/tests/timeout/t0001.good --- smenu-0.9.14/tests/timeout/t0001.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/timeout/t0001.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ [ 0s before selecting the current highlighted word] 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 51:01 52:01 53:01 + + a b c 2:07 $ diff -Nru smenu-0.9.14/tests/timeout/t0002.good smenu-0.9.15/tests/timeout/t0002.good --- smenu-0.9.14/tests/timeout/t0002.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/timeout/t0002.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ [ 0s before selecting the current highlighted word] 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 51:01 52:01 53:01 + + a b c 0:07 $ diff -Nru smenu-0.9.14/tests/timeout/t0003.good smenu-0.9.15/tests/timeout/t0003.good --- smenu-0.9.14/tests/timeout/t0003.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/timeout/t0003.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ [ 0s before quitting without selecting anything] 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 + + a b c 2:07 $ diff -Nru smenu-0.9.14/tests/timeout/t0004.good smenu-0.9.15/tests/timeout/t0004.good --- smenu-0.9.14/tests/timeout/t0004.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/timeout/t0004.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ [ 0s before selecting the word "test!"] 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 + + a b c 2:07 $ diff -Nru smenu-0.9.14/tests/timeout/t0005.good smenu-0.9.15/tests/timeout/t0005.good --- smenu-0.9.14/tests/timeout/t0005.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/timeout/t0005.good 2019-03-30 14:08:14.000000000 +0000 @@ -2,6 +2,8 @@ [ 3s before selecting the current highlighted word] 0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 40:01 41:01 42:01 43:01 44:01 45:01 46:01 47:01 48:01 49:01 50:01 51:01 52:01 53:01 + + a b c 2:07 $ diff -Nru smenu-0.9.14/tests/utf8/t0003.good smenu-0.9.15/tests/utf8/t0003.good --- smenu-0.9.14/tests/utf8/t0003.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/utf8/t0003.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,9 @@ $ OUT=$(LC_ALL=C smenu -M -m «←→» t0003.in) .... -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 +36:01 37:01 38:01 39:01 + + ... ... ... 33:07 34:07 35:07 $ diff -Nru smenu-0.9.14/tests/utf8/t0004.good smenu-0.9.15/tests/utf8/t0004.good --- smenu-0.9.14/tests/utf8/t0004.good 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/tests/utf8/t0004.good 2019-03-30 14:08:14.000000000 +0000 @@ -1,7 +1,9 @@ $ OUT=$(LC_ALL=en_US.UTF-8 smenu -M -m «←→» t0003.in) «←→» -0:01 1:01 2:01 3:01 4:01 5:01 6:01 7:01 8:01 9:01 10:01 11:01 12:01 13:01 14:01 15:01 16:01 17:01 18:01 19:01 20:01 21:01 22:01 23:01 24:01 25:01 26:01 27:01 28:01 29:01 30:01 31:01 32:01 33:01 34:01 35:01 36:01 37:01 38:01 39:01 +36:01 37:01 38:01 39:01 + + éé€ éé€ é€é 33:07 34:07 35:07 $ diff -Nru smenu-0.9.14/TODO smenu-0.9.15/TODO --- smenu-0.9.14/TODO 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/TODO 2019-03-30 14:08:14.000000000 +0000 @@ -2,4 +2,3 @@ # ========================================. I: Improve configure.ac I: Improve the checking of system/library calls returns -I: Cleanup the code and split it into several files diff -Nru smenu-0.9.14/usage.c smenu-0.9.15/usage.c --- smenu-0.9.14/usage.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/usage.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,166 @@ +/* *************** */ +/* Usage functions */ +/* *************** */ + +#include +#include +#include + +#include "usage.h" + +/* =================== */ +/* Short usage display */ +/* =================== */ +void +short_usage(int must_exit) +{ + printf("\nUsage: smenu [-h|-?] [-f config_file] [-n [lines]] "); + printf("[-t [cols]] [-k] [-v] \\\n"); + printf(" [-s pattern] [-m message] [-w] [-d] [-M] [-c] [-l] "); + printf("[-r] [-b] \\\n"); + printf(" [-a prefix:attr [prefix:attr]...] "); + printf("[-i regex] [-e regex] \\\n"); + printf(" [-C [i|e]] "); + printf("[-R [i|e]] \\\n"); + printf(" [-S /regex/repl/[g][v][s][i]] "); + printf("[-I /regex/repl/[g][v][s][i]] \\\n"); + printf(" [-E /regex/repl/[g][v][s][i]] "); + printf("[-A regex] [-Z regex] \\\n"); + printf(" [-N [regex]] [-U [regex]] [-F] [-D arg...] "); + printf(" \\\n"); + printf(" [-1 regex [attr]] [-2 regex [attr]]... "); + printf("[-5 regex [attr]] [-g [string]] \\\n"); + printf(" [-q] [-W bytes] [-L bytes] [-T [separator]] "); + printf("[-P [separator]] [-p] \\\n"); + printf(" [-V] [-x|-X current|quit|word [] "); + printf("] \\\n"); + printf(" [-/ prefix|substring|fuzzy] [--] [input_file]\n\n"); + printf(" ::= col1[-col2]...|...\n"); + printf(" ::= row1[-row2]...|...\n"); + printf(" ::= i|e|c|b|s|m|t|ct|sf|st|mf|mt|"); + printf("sfe|ste|mfe|mte|da\n"); + printf(" ::= [l|r:]|[a:l|r]|[p:i|a]|"); + printf("[w:]|\n"); + printf(" [f:y|n]|[o:[+]]|[n:]|"); + printf("[i:]|[d:]|\n"); + printf(" [s:]|[h:t|c|k]\n"); + printf(" Ex: l:'(' a:l\n"); + printf(" ::= [fg][/bg][,style] \n"); + printf(" Ex: 7/4,bu\n"); + printf(" ::= regex \n"); + printf(" Ex: /regex/ or :regex:\n\n"); + printf(" and can be freely mixed "); + printf("when used\n"); + printf(" with -C and -R (ex: 2,/x/,4-5).\n\n"); + + if (must_exit) + exit(EXIT_FAILURE); +} + +/* ====================== */ +/* Usage display and exit */ +/* ====================== */ +void +usage(void) +{ + short_usage(0); + + printf("This is a filter that gets words from stdin "); + printf("and outputs the selected\n"); + printf("words (or nothing) on stdout in a nice selection "); + printf("window\n\n"); + printf("The selection window appears on /dev/tty "); + printf("immediately after the current line\n"); + printf("(no clear screen!).\n\n"); + printf("The following options are available:\n\n"); + printf("-h displays this help.\n"); + printf("-f selects an alternative configuration file.\n"); + printf("-n sets the number of lines in the selection window.\n"); + printf("-t tabulates the items. The number of columns can be limited " + "with\n"); + printf(" an optional number.\n"); + printf("-k does not trim spaces surrounding the output string if any.\n"); + printf("-v makes the bell visual (fuzzy search with error).\n"); + printf("-s sets the initial cursor position (read the manual for " + "more details).\n"); + printf("-m displays a one-line message above the window.\n"); + printf("-w uses all the terminal width for the columns if their numbers " + "is given.\n"); + printf("-d deletes the selection window on exit.\n"); + printf("-M centers the display if possible.\n"); + printf("-c is like -t without argument but respects end of lines.\n"); + printf("-l is like -c without column alignments.\n"); + printf("-r enables ENTER to validate the selection even in " + "search mode.\n"); + printf("-b displays the non printable characters as space.\n"); + printf("-a sets the attributes for the various displayed "); + printf("elements.\n"); + printf("-i sets the regex input filter to match the selectable words.\n"); + printf("-e sets the regex input filter to match the non-selectable " + "words.\n"); + printf("-C sets columns restrictions for selections.\n"); + printf("-R sets rows restrictions for selections.\n"); + printf("-S sets the post-processing action to apply to all words.\n"); + printf("-I sets the post-processing action to apply to selectable " + "words only.\n"); + printf("-E sets the post-processing action to apply to non-selectable " + "words only.\n"); + printf("-A forces a class of words to be the first of the line they " + "appear in.\n"); + printf("-Z forces a class of words to be the latest of the line they " + "appear in.\n"); + printf("-N/-U numbers/un-numbers and provides a direct access to words " + "matching\n"); + printf(" (or not) a specific regex.\n"); + printf("-F numbers and provides a direct access to words by extracting the " + "number\n"); + printf(" from the words.\n"); + printf("-D sets sub-options to modify the behaviour of -N, -U and -F.\n"); + printf("-1,-2,...,-5 gives specific colors to up to 5 classes of " + "selectable words.\n"); + printf("-g separates columns with a character in column or tabulate " + "mode.\n"); + printf("-q prevents the display of the scroll bar.\n"); + printf("-W sets the input words separators.\n"); + printf("-L sets the input lines separators.\n"); + printf("-T/-P enables the tagging (multi-selections) mode. "); + printf("An optional parameter\n"); + printf(" sets the separator string between the selected words "); + printf("on the output.\n"); + printf(" A single space is the default separator.\n"); + printf("-p activates the auto-tagging when using -T or -P.\n"); + printf("-V displays the current version and quits.\n"); + printf("-x|-X sets a timeout and specifies what to do when it expires.\n"); + printf("-/ changes the affectation of the / key (default fuzzy search).\n"); + printf("\nNavigation keys are:\n"); + printf(" - Left/Down/Up/Right arrows or h/j/k/l, H/J/K/L.\n"); + printf(" - Home/End, SHIFT|CTRL+Home/End CTRK+J/CTRL+K.\n"); + printf(" - Numbers if some words are numbered (-N/-U/-F).\n"); + printf(" - SPACE to search for the next match of a previously\n"); + printf(" entered search prefix if any, see below.\n\n"); + printf("Other useful keys are:\n"); + printf(" - Help key (temporary display of a short help line): " + "?\n"); + printf(" - Exit key without output (do nothing) : " + "q\n"); + printf(" - Tagging keys: Select/Deselect/Toggle : " + "INS/DEL/t\n"); + printf(" - Selection key : " + "ENTER\n"); + printf(" - Cancel key : " + "ESC\n"); + printf(" - Search key : " + "/ or CTRL-F\n\n"); + printf("The search key activates a timed search mode in which\n"); + printf("you can enter the first letters of the searched word.\n"); + printf("When entering this mode you have 7s to start typing\n"); + printf("and each entered letter gives you 5 more seconds before\n"); + printf("the timeout. After that the search mode is ended.\n\n"); + printf("Notes:\n"); + printf("- the timer can be cancelled by pressing ESC.\n"); + printf("- a bad search letter can be removed with "); + printf("CTRL-H or Backspace.\n\n"); + printf("(C) Pierre Gentile (2015).\n\n"); + + exit(EXIT_FAILURE); +} diff -Nru smenu-0.9.14/usage.h smenu-0.9.15/usage.h --- smenu-0.9.14/usage.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/usage.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,15 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef USAGE_H +#define USAGE_H + +void +short_usage(int must_exit); + +void +usage(void); + +#endif diff -Nru smenu-0.9.14/utf8.c smenu-0.9.15/utf8.c --- smenu-0.9.14/utf8.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/utf8.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,446 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +/* ************************************ */ +/* Various UTF-8 manipulation functions */ +/* ************************************ */ + +#include +#include +#include +#include +#include +#include +#include +#include "xmalloc.h" +#include "utf8.h" + +/* ======================================================================== */ +/* Unicode (UTF-8) ascii representation interpreter. */ +/* The string passed will be altered but will not move in memory */ +/* All sequence of \uxx, \uxxxx, \uxxxxxx and \uxxxxxxxx will be replace by */ +/* the corresponding UTF-8 character. */ +/* ======================================================================== */ +void +utf8_interpret(char * s, langinfo_t * langinfo) +{ + char * utf8_str; /* \uxx... */ + size_t utf8_to_eos_len; /* bytes in s starting from the first * + * occurrence of \u */ + size_t init_len; /* initial lengths of the string to interpret */ + size_t utf8_ascii_len; /* 2,4,6 or 8 bytes */ + size_t len_to_remove = 0; /* number of bytes to remove after the conversion */ + char tmp[9]; /* temporary string */ + + /* Guard against the case where s is NULL */ + /* """""""""""""""""""""""""""""""""""""" */ + if (s == NULL) + return; + + init_len = strlen(s); + + while ((utf8_str = strstr(s, "\\u")) != NULL) + { + utf8_to_eos_len = strlen(utf8_str); + if (utf8_to_eos_len < 4) /* string too short to contain * + * a valid UTF-8 char */ + { + *utf8_str = '.'; + *(utf8_str + 1) = '\0'; + } + else /* s is long enough */ + { + unsigned byte; + char * utf8_seq_offset = utf8_str + 2; + + /* Get the first 2 utf8 bytes */ + *tmp = *utf8_seq_offset; + *(tmp + 1) = *(utf8_seq_offset + 1); + *(tmp + 2) = '\0'; + + /* If they are invalid, replace the \u sequence by a dot */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""" */ + if (!isxdigit(tmp[0]) || !isxdigit(tmp[1])) + { + *utf8_str = '.'; + if (4 >= utf8_to_eos_len) + *(utf8_str + 1) = '\0'; + else + memmove(utf8_str, utf8_str + 4, utf8_to_eos_len - 4); + return; + } + else + { + /* They are valid, deduce from them the length of the sequence */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + sscanf(tmp, "%2x", &byte); + utf8_ascii_len = utf8_get_length(byte) * 2; + + /* Check again if the inputs string is long enough */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + if (utf8_to_eos_len - 2 < utf8_ascii_len) + { + *utf8_str = '.'; + *(utf8_str + 1) = '\0'; + } + else + { + /* replace the \u sequence by the bytes forming the UTF-8 char */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + size_t i; + *tmp = byte; + + /* Put the bytes in the tmp string */ + /* ''''''''''''''''''''''''''''''' */ + if (langinfo->utf8) + { + for (i = 1; i < utf8_ascii_len / 2; i++) + { + sscanf(utf8_seq_offset + 2 * i, "%2x", &byte); + *(tmp + i) = byte; + } + tmp[utf8_ascii_len / 2] = '\0'; + } + + /* Does they form a valid UTF-8 char? */ + /* '''''''''''''''''''''''''''''''''' */ + if (langinfo->utf8 && utf8_validate(tmp, utf8_ascii_len / 2)) + { + /* Put them back in the original string and move */ + /* the remaining bytes after them */ + /* ''''''''''''''''''''''''''''''''''''''''''''' */ + memmove(utf8_str, tmp, utf8_ascii_len / 2); + + if (utf8_to_eos_len < utf8_ascii_len) + *(utf8_str + utf8_ascii_len / 2 + 1) = '\0'; + else + memmove(utf8_str + utf8_ascii_len / 2, + utf8_seq_offset + utf8_ascii_len, + utf8_to_eos_len - utf8_ascii_len - 2 + 1); + } + else + { + /* The invalid sequence is replaced by a dot */ + /* ''''''''''''''''''''''''''''''''''''''''' */ + *utf8_str = '.'; + if (utf8_to_eos_len < utf8_ascii_len) + *(utf8_str + 1) = '\0'; + else + memmove(utf8_str + 1, utf8_seq_offset + utf8_ascii_len, + utf8_to_eos_len - utf8_ascii_len - 2 + 1); + utf8_ascii_len = 2; + } + } + + /* Update the number of bytes to remove at the end */ + /* of the initial string */ + /* """"""""""""""""""""""""""""""""""""""""""""""" */ + len_to_remove += 2 + utf8_ascii_len / 2; + } + } + } + + /* Make sure that the string is well terminated */ + /* """""""""""""""""""""""""""""""""""""""""""" */ + *(s + init_len - len_to_remove) = '\0'; + + return; +} + +/* ========================================================= */ +/* Decode the number of bytes taken by a character (UTF-8) */ +/* It is the length of the leading sequence of bits set to 1 */ +/* (Count Leading Ones) */ +/* ========================================================= */ +int +utf8_get_length(unsigned char c) +{ + if (c < 0x80) + return 1; + else if (c < 0xe0) + return 2; + else if (c < 0xf0) + return 3; + else + return 4; +} + +/* ================================================== */ +/* Return the byte offset of the nth UTF-8 glyph in s */ +/* ================================================== */ +size_t +utf8_offset(char * s, size_t n) +{ + size_t i = 0; + + while (n > 0) + { + if (s[i++] & 0x80) + { + (void)(((s[++i] & 0xc0) != 0x80) || ((s[++i] & 0xc0) != 0x80) || ++i); + } + n--; + } + return i; +} + +/* ============================================== */ +/* Points to the previous UTF-8 glyph in a string */ +/* from the given position */ +/* ============================================== */ +char * +utf8_prev(const char * str, const char * p) +{ + while ((*p & 0xc0) == 0x80) + p--; + + for (--p; p >= str; --p) + { + if ((*p & 0xc0) != 0x80) + return (char *)p; + } + return NULL; +} + +/* ========================================== */ +/* Points to the next UTF-8 glyph in a string */ +/* from the current position */ +/* ========================================== */ +char * +utf8_next(char * p) +{ + if (*p) + { + for (++p; (*p & 0xc0) == 0x80; ++p) + ; + } + return (*p == '\0' ? NULL : p); +} + +/* ============================================================ */ +/* Replace any UTF-8 glyph present in s by a dot in-place */ +/* s will be modified but its address in memory will not change */ +/* ============================================================ */ +void +utf8_sanitize(char * s) +{ + char * p = s; + int n; + size_t len; + + len = strlen(s); + while (*p) + { + n = utf8_get_length(*p); + if (n > 1) + { + *p = '.'; + memmove(p + 1, p + n, len - (p - s) - n + 1); + len -= (n - 1); + } + p++; + } +} + +static const char trailing_bytes_for_utf8[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 +}; + +/* ================================================================== */ +/* UTF-8 validation routine inspired by Jeff Bezanson */ +/* placed in the public domain Fall 2005 */ +/* (https://github.com/JeffBezanson/cutef8) */ +/* */ +/* Returns 1 if str contains a valid UTF-8 byte sequence, 0 otherwise */ +/* ================================================================== */ +int +utf8_validate(const char * str, size_t length) +{ + const unsigned char *p, *pend = (const unsigned char *)str + length; + unsigned char c; + size_t ab; + + for (p = (const unsigned char *)str; p < pend; p++) + { + c = *p; + if (c < 128) + continue; + if ((c & 0xc0) != 0xc0) + return 0; + ab = trailing_bytes_for_utf8[c]; + if (length < ab) + return 0; + length -= ab; + + p++; + /* Check top bits in the second byte */ + /* """"""""""""""""""""""""""""""""" */ + if ((*p & 0xc0) != 0x80) + return 0; + + /* Check for overlong sequences for each different length */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""" */ + switch (ab) + { + /* Check for xx00 000x */ + /* """"""""""""""""""" */ + case 1: + if ((c & 0x3e) == 0) + return 0; + continue; /* We know there aren't any more bytes to check */ + + /* Check for 1110 0000, xx0x xxxx */ + /* """""""""""""""""""""""""""""" */ + case 2: + if (c == 0xe0 && (*p & 0x20) == 0) + return 0; + break; + + /* Check for 1111 0000, xx00 xxxx */ + /* """""""""""""""""""""""""""""" */ + case 3: + if (c == 0xf0 && (*p & 0x30) == 0) + return 0; + break; + + /* Check for 1111 1000, xx00 0xxx */ + /* """""""""""""""""""""""""""""" */ + case 4: + if (c == 0xf8 && (*p & 0x38) == 0) + return 0; + break; + + /* Check for leading 0xfe or 0xff, */ + /* and then for 1111 1100, xx00 00xx */ + /* """"""""""""""""""""""""""""""""" */ + case 5: + if (c == 0xfe || c == 0xff || (c == 0xfc && (*p & 0x3c) == 0)) + return 0; + break; + } + + /* Check for valid bytes after the 2nd, if any; all must start 10 */ + /* """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + while (--ab > 0) + { + if ((*(++p) & 0xc0) != 0x80) + return 0; + } + } + + return 1; +} + +/* ====================== */ +/* Multibyte UTF-8 strlen */ +/* ====================== */ +size_t +utf8_strlen(char * str) +{ + size_t i = 0, j = 0; + + while (str[i]) + { + if ((str[i] & 0xc0) != 0x80) + j++; + i++; + } + return j; +} + +/* =================================================================== */ +/* Multibytes extraction of the prefix of n UTF-8 glyphs from a string */ +/* The destination string d must have been allocated before. */ +/* pos is updated to reflect the position AFTER the prefix. */ +/* =================================================================== */ +char * +utf8_strprefix(char * d, char * s, long n, long * pos) +{ + long i = 0; + long j = 0; + + *pos = 0; + + while (s[i] && j < n) + { + d[i] = s[i]; + i++; + j++; + while (s[i] && (s[i] & 0xC0) == 0x80) + { + d[i] = s[i]; + i++; + } + } + + *pos = i; + + d[i] = '\0'; + + return d; +} + +/* ================================================ */ +/* Convert a UTF-8 glyph string to a wchar_t string */ +/* ================================================ */ +wchar_t * +utf8_strtowcs(char * s) +{ + int converted = 0; + unsigned char * ch; + wchar_t * wptr, *w; + size_t size; + + size = (long)strlen(s); + w = xmalloc((size + 1) * sizeof(wchar_t)); + w[0] = L'\0'; + + wptr = w; + for (ch = (unsigned char *)s; *ch; ch += converted) + { + if ((converted = mbtowc(wptr, (char *)ch, 4)) > 0) + wptr++; + else + { + *wptr++ = (wchar_t)*ch; + converted = 1; + } + } + + *wptr = L'\0'; + + return w; +} + +/* ============================================================== */ +/* Fill dst whi a lowercase ocopy of src whar the character is an */ +/* ascci one. dsk must be preallocated before the call. */ +/* ============================================================== */ +void +utf8_strtolower(char * dst, char * src) +{ + unsigned char c; + + while ((c = *src)) + { + if (c >= 0x80) + *dst = c; + else + *dst = tolower(c); + + src++; + dst++; + } + *dst = '\0'; +} diff -Nru smenu-0.9.14/utf8.h smenu-0.9.15/utf8.h --- smenu-0.9.14/utf8.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/utf8.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,52 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef UTF8_H +#define UTF8_H + +typedef struct langinfo_s langinfo_t; + +/* Locale informations */ +/* """"""""""""""""""" */ +struct langinfo_s +{ + int utf8; /* charset is UTF-8 */ + int bits; /* number of bits in the charset */ +}; + +int +utf8_get_length(unsigned char c); + +size_t +utf8_offset(char *, size_t); + +char * +utf8_strprefix(char * d, char * s, long n, long * pos); + +size_t +utf8_strlen(char * str); + +wchar_t * +utf8_strtowcs(char * s); + +void +utf8_sanitize(char * s); + +void +utf8_interpret(char * s, langinfo_t * langinfo); + +int +utf8_validate(const char * str, size_t length); + +char * +utf8_prev(const char * str, const char * p); + +char * +utf8_next(char * p); + +void +utf8_strtolower(char * dst, char * src); + +#endif diff -Nru smenu-0.9.14/utils.c smenu-0.9.15/utils.c --- smenu-0.9.14/utils.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/utils.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,281 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +/* ******************************* */ +/* Various small utility functions */ +/* ******************************* */ + +#include "config.h" +#include +#include +#include +#include +#include +#include "xmalloc.h" +#include "list.h" +#include "utils.h" + +/* ****************** */ +/* Interval functions */ +/* ****************** */ + +/* ===================== */ +/* Create a new interval */ +/* ===================== */ +interval_t * +interval_new(void) +{ + interval_t * ret = xmalloc(sizeof(interval_t)); + + return ret; +} + +/* ====================================== */ +/* Compare 2 intervals as integer couples */ +/* same return values as for strcmp */ +/* ====================================== */ +int +interval_comp(void * a, void * b) +{ + interval_t * ia = (interval_t *)a; + interval_t * ib = (interval_t *)b; + + if (ia->low < ib->low) + return -1; + if (ia->low > ib->low) + return 1; + if (ia->high < ib->high) + return -1; + if (ia->high > ib->high) + return 1; + + return 0; +} + +/* ================================ */ +/* Swap the values of two intervals */ +/* ================================ */ +void +interval_swap(void * a, void * b) +{ + interval_t * ia = (interval_t *)a; + interval_t * ib = (interval_t *)b; + long tmp; + + tmp = ia->low; + ia->low = ib->low; + ib->low = tmp; + + tmp = ia->high; + ia->high = ib->high; + ib->high = tmp; +} + +/* ===================================================================== */ +/* Merge the intervals from an interval list in order to get the minimum */ +/* number of intervals to consider. */ +/* ===================================================================== */ +void +merge_intervals(ll_t * list) +{ + ll_node_t * node1, *node2; + interval_t *data1, *data2; + + if (!list || list->len < 2) + return; + else + { + /* Step 1: sort the intervals list */ + /* """"""""""""""""""""""""""""""" */ + ll_sort(list, interval_comp, interval_swap); + + /* Step 2: merge the list by merging the consecutive intervals */ + /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + node1 = list->head; + node2 = node1->next; + + while (node2) + { + data1 = (interval_t *)(node1->data); + data2 = (interval_t *)(node2->data); + + if (data1->high >= data2->low - 1) + { + /* Interval 1 overlaps interval 2 */ + /* '''''''''''''''''''''''''''''' */ + if (data2->high >= data1->high) + data1->high = data2->high; + ll_delete(list, node2); + free(data2); + node2 = node1->next; + } + else + { + /* No overlap */ + /* '''''''''' */ + node1 = node2; + node2 = node2->next; + } + } + } +} + +/* ***************** */ +/* Strings functions */ +/* ***************** */ + +/* ========================================================================= */ +/* Allocate memory and safely concatenate strings. Stolen from a public */ +/* domain implementation which can be found here: */ +/* http://openwall.info/wiki/people/solar/software/public-domain-source-code */ +/* ========================================================================= */ +char * +concat(const char * s1, ...) +{ + va_list args; + const char * s; + char * p, *result; + size_t l, m, n; + + m = n = strlen(s1); + va_start(args, s1); + while ((s = va_arg(args, char *))) + { + l = strlen(s); + if ((m += l) < l) + break; + } + va_end(args); + if (s || m >= INT_MAX) + return NULL; + + result = (char *)xmalloc(m + 1); + + memcpy(p = result, s1, n); + p += n; + va_start(args, s1); + while ((s = va_arg(args, char *))) + { + l = strlen(s); + if ((n += l) < l || n > m) + break; + memcpy(p, s, l); + p += l; + } + va_end(args); + if (s || m != n || p != result + n) + { + free(result); + return NULL; + } + + *p = 0; + return result; +} + +/* =============================================== */ +/* Is the string str2 a prefix of the string str1? */ +/* =============================================== */ +int +strprefix(char * str1, char * str2) +{ + while (*str1 != '\0' && *str1 == *str2) + { + str1++; + str2++; + } + + return *str2 == '\0'; +} + +/* ======================= */ +/* Trim leading characters */ +/* ======================= */ +void +ltrim(char * str, const char * trim_str) +{ + size_t len = strlen(str); + size_t begin = strspn(str, trim_str); + size_t i; + + if (begin > 0) + for (i = begin; i <= len; ++i) + str[i - begin] = str[i]; +} + +/* ================================================= */ +/* Trim trailing characters */ +/* The resulting string will have at least min bytes */ +/* even if trailing spaces remain. */ +/* ================================================= */ +void +rtrim(char * str, const char * trim_str, size_t min) +{ + size_t len = strlen(str); + while (len > min && strchr(trim_str, str[len - 1])) + str[--len] = '\0'; +} + +/* ========================================= */ +/* Case insensitive strcmp */ +/* from http://c.snippets.org/code/stricmp.c */ +/* ========================================= */ +int +my_strcasecmp(const char * str1, const char * str2) +{ +#ifdef HAVE_STRCASECMP + return strcasecmp(str1, str2); +#else + int retval = 0; + + while (1) + { + retval = tolower(*str1++) - tolower(*str2++); + + if (retval) + break; + + if (*str1 && *str2) + continue; + else + break; + } + return retval; +#endif +} + +/* =========================================== */ +/* memmove based strcpy (tolerates overlaping) */ +/* =========================================== */ +char * +my_strcpy(char * str1, char * str2) +{ + if (str1 == NULL || str2 == NULL) + return NULL; + + memmove(str1, str2, strlen(str2) + 1); + + return str1; +} + +/* =============================== */ +/* 7 bits aware version of isprint */ +/* =============================== */ +int +isprint7(int i) +{ + return (i >= 0x20 && i <= 0x7e); +} + +/* =============================== */ +/* 8 bits aware version of isprint */ +/* =============================== */ +int +isprint8(int i) +{ + unsigned char c = i & (unsigned char)0xff; + + return (c >= 0x20 && c < 0x7f) || (c >= (unsigned char)0xa0); +} diff -Nru smenu-0.9.14/utils.h smenu-0.9.15/utils.h --- smenu-0.9.14/utils.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/utils.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,62 @@ +/* ########################################################### */ +/* This Software is licensed under the GPL licensed Version 2, */ +/* please read http://www.gnu.org/copyleft/gpl.html */ +/* ########################################################### */ + +#ifndef UTILS_H +#define UTILS_H + +typedef struct interval_s interval_t; +typedef struct range_s range_t; + +struct interval_s +{ + long low; + long high; +}; + +/* Structure used by the replace function to delimit matches */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */ +struct range_s +{ + size_t start; + size_t end; +}; + +interval_t * +interval_new(void); + +int +interval_comp(void * a, void * b); + +void +interval_swap(void * a, void * b); + +void +merge_intervals(ll_t * list); + +char * +concat(const char * str1, ...); + +int +strprefix(char * str1, char * str2); + +void +ltrim(char * str, const char * trim); + +void +rtrim(char * str, const char * trim, size_t min_len); + +int +my_strcasecmp(const char * str1, const char * str2); + +char * +my_strcpy(char * dst, char * src); + +int +isprint7(int i); + +int +isprint8(int i); + +#endif diff -Nru smenu-0.9.14/version smenu-0.9.15/version --- smenu-0.9.14/version 2018-09-04 21:43:56.000000000 +0000 +++ smenu-0.9.15/version 2019-03-30 14:08:14.000000000 +0000 @@ -1 +1 @@ -0.9.14 +0.9.15 diff -Nru smenu-0.9.14/xmalloc.c smenu-0.9.15/xmalloc.c --- smenu-0.9.14/xmalloc.c 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/xmalloc.c 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,121 @@ +/* *************************** */ +/* Memory management functions */ +/* *************************** */ + +/* Created by Kevin Locke (from numerous canonical examples) */ +/* */ +/* Adapted for use by smenu */ +/* */ +/* I hereby place this file in the public domain. It may be freely */ +/* reproduced, distributed, used, modified, built upon, or otherwise */ +/* employed by anyone for any purpose without restriction. */ +/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */ + +#include +#include +#include +#include + +#include "xmalloc.h" + +/* ================= */ +/* Customized malloc */ +/* ================= */ +void * +xmalloc(size_t size) +{ + void * allocated; + size_t real_size; + + real_size = (size > 0) ? size : 1; + allocated = malloc(real_size); + if (allocated == NULL) + { + fprintf(stderr, + "Error: Insufficient memory (attempt to malloc %lu bytes)\n", + (unsigned long int)size); + + exit(EXIT_FAILURE); + } + + return allocated; +} + +/* ================= */ +/* Customized calloc */ +/* ================= */ +void * +xcalloc(size_t n, size_t size) +{ + void * allocated; + + n = (n > 0) ? n : 1; + size = (size > 0) ? size : 1; + allocated = calloc(n, size); + if (allocated == NULL) + { + fprintf(stderr, + "Error: Insufficient memory (attempt to calloc %lu bytes)\n", + (unsigned long int)size); + + exit(EXIT_FAILURE); + } + + return allocated; +} + +/* ================== */ +/* Customized realloc */ +/* ================== */ +void * +xrealloc(void * p, size_t size) +{ + void * allocated; + + allocated = realloc(p, size); + if (allocated == NULL && size > 0) + { + fprintf(stderr, + "Error: Insufficient memory (attempt to xrealloc %lu bytes)\n", + (unsigned long int)size); + + exit(EXIT_FAILURE); + } + + return allocated; +} + +/* =================================== */ +/* strdup implementation using xmalloc */ +/* =================================== */ +char * +xstrdup(const char * p) +{ + char * allocated; + + allocated = xmalloc(strlen(p) + 1); + strcpy(allocated, p); + + return allocated; +} + +/* ================================================== */ +/* strndup implementation using xmalloc */ +/* This version guarantees that there is a final '\0' */ +/* ================================================== */ +char * +xstrndup(const char * str, size_t len) +{ + char * p; + + p = memchr(str, '\0', len); + + if (p) + len = p - str; + + p = xmalloc(len + 1); + memcpy(p, str, len); + p[len] = '\0'; + + return p; +} diff -Nru smenu-0.9.14/xmalloc.h smenu-0.9.15/xmalloc.h --- smenu-0.9.14/xmalloc.h 1970-01-01 00:00:00.000000000 +0000 +++ smenu-0.9.15/xmalloc.h 2019-03-30 14:08:14.000000000 +0000 @@ -0,0 +1,19 @@ +#ifndef XMALLOC_H +#define XMALLOC_H + +void * +xmalloc(size_t size); + +void * +xcalloc(size_t num, size_t size); + +void * +xrealloc(void * ptr, size_t size); + +char * +xstrdup(const char * p); + +char * +xstrndup(const char * str, size_t len); + +#endif