From f5681dd8e904fb288ba6365d44fce4d8dd2bf263 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Wed, 10 Sep 2014 22:21:58 -0400 Subject: [PATCH 1/8] initial zsh support --- CMakeLists.txt | 4 + README-ZSH.md | 8 ++ .../zsh_functions/final_term_control_sequence | 11 +++ .../zsh_functions/send_control_sequence | 1 + data/Startup/zsh_functions/send_progress | 1 + data/Startup/zsh_functions/text_menu_end | 1 + data/Startup/zsh_functions/text_menu_start | 3 + data/Startup/zsh_startup.in | 83 +++++++++++++++++++ data/Termlets/ls | 7 ++ data/Termlets/ps | 7 ++ data/Termlets/wget | 6 ++ data/org.gnome.finalterm.gschema.xml | 2 +- src/Terminal.vala | 23 ++++- 13 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 README-ZSH.md create mode 100644 data/Startup/zsh_functions/final_term_control_sequence create mode 100644 data/Startup/zsh_functions/send_control_sequence create mode 100644 data/Startup/zsh_functions/send_progress create mode 100644 data/Startup/zsh_functions/text_menu_end create mode 100644 data/Startup/zsh_functions/text_menu_start create mode 100644 data/Startup/zsh_startup.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 1de6862..588a1ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ if (NOT MINIMAL_FLAGS) endif (NOT MINIMAL_FLAGS) configure_file(${CMAKE_SOURCE_DIR}/data/Startup/bash_startup.in ${CMAKE_BINARY_DIR}/Startup/bash_startup @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Startup/zsh_startup.in ${CMAKE_BINARY_DIR}/Startup/zsh_startup @ONLY) set(PKGS clutter-gtk-1.0 mx-1.0 keybinder-3.0 gee-0.8) @@ -104,6 +105,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ColorSchemes DESTINATION shar install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/KeyBindings DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_BINARY_DIR}/Startup DESTINATION share/finalterm) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/data/Startup/preexec.bash DESTINATION share/finalterm/Startup) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Startup/zsh_functions DESTINATION share/finalterm/Startup) +file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/zsh_functions/*") +install(FILES ${files} DESTINATION share/finalterm/Stertup/zsh_functions) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/TerminalCommands DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Termlets DESTINATION share/finalterm FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/TextMenus DESTINATION share/finalterm) diff --git a/README-ZSH.md b/README-ZSH.md new file mode 100644 index 0000000..c261cef --- /dev/null +++ b/README-ZSH.md @@ -0,0 +1,8 @@ +Requirements +- gsettings set org.gnome.finalterm shell-path /usr/bin/zsh +- add the following to ~/.zshrc: +``` +if [ -n "$FINALTERMSCRIPT" ]; then + . $FINALTERMSCRIPT +fi +``` diff --git a/data/Startup/zsh_functions/final_term_control_sequence b/data/Startup/zsh_functions/final_term_control_sequence new file mode 100644 index 0000000..35ac81a --- /dev/null +++ b/data/Startup/zsh_functions/final_term_control_sequence @@ -0,0 +1,11 @@ +# NOTE: xterm properly ignores sequences of this type as unknown, +# while some other terminals (such as GNOME Terminal) print them +control_sequence="\e]133;" +for argument in "$@"; do + control_sequence="$control_sequence$argument;" +done +# TODO: Remove last semicolon +control_sequence="$control_sequence\a" + +# TODO: Should "-ne" be added here? +echo "$control_sequence" diff --git a/data/Startup/zsh_functions/send_control_sequence b/data/Startup/zsh_functions/send_control_sequence new file mode 100644 index 0000000..e10a5d5 --- /dev/null +++ b/data/Startup/zsh_functions/send_control_sequence @@ -0,0 +1 @@ +echo -ne "$1" diff --git a/data/Startup/zsh_functions/send_progress b/data/Startup/zsh_functions/send_progress new file mode 100644 index 0000000..e3e0411 --- /dev/null +++ b/data/Startup/zsh_functions/send_progress @@ -0,0 +1 @@ +send_control_sequence "$(final_term_control_sequence 'G' "$1" "$2")" diff --git a/data/Startup/zsh_functions/text_menu_end b/data/Startup/zsh_functions/text_menu_end new file mode 100644 index 0000000..061f4b1 --- /dev/null +++ b/data/Startup/zsh_functions/text_menu_end @@ -0,0 +1 @@ +echo "$(final_term_control_sequence 'F' "$1")" diff --git a/data/Startup/zsh_functions/text_menu_start b/data/Startup/zsh_functions/text_menu_start new file mode 100644 index 0000000..6eb63a3 --- /dev/null +++ b/data/Startup/zsh_functions/text_menu_start @@ -0,0 +1,3 @@ + # NOTE: Nested double quotes look strange, but are both valid and necessary; + # see http://stackoverflow.com/questions/4031007 + echo "$(final_term_control_sequence 'E' "$1")" diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in new file mode 100644 index 0000000..2363dbc --- /dev/null +++ b/data/Startup/zsh_startup.in @@ -0,0 +1,83 @@ +#!/usr/bin/zsh + +# Final Term's customizations start here +finalterm_fpath="@PKGDATADIR@/Startup/zsh_functions" +if [ -d $finalterm_fpath ]; then + fpath=($finalterm_fpath $fpath) +fi +FPATH="${finalterm_fpath}:${FPATH}" +export FPATH + +autoload send_control_sequence +autoload final_term_control_sequence + +# Logic for prompt and command detection +function send_return_code() { + # Send sequence containing the return code of the last command + send_control_sequence "$(final_term_control_sequence 'D' "$?")" +} + +precmd() { + # Send sequence marking a command prompt + send_control_sequence "$(final_term_control_sequence 'A')" +} + +preexec() { + # Send sequence containing the command to be executed + send_control_sequence "$(final_term_control_sequence 'C' "$1")" +} + +PROMPT_COMMAND=send_return_code;$PROMPT_COMMAND + +# Send sequence marking the start of a command +PS1=$PS1$(final_term_control_sequence 'B') + + +# Logic for terminal commands +function trim() { + local var=$1 + var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters + var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters + echo -n "$var" +} + +function send_commands() { + send_control_sequence "$(final_term_control_sequence 'H' "$1" '#' "${@:2}")" +} + +pushd "@PKGDATADIR@/TerminalCommands" > /dev/null +while IFS= read -r line; do + stripped_line=$(trim "$line") + + if [ -n "$stripped_line" ]; then + # Non-empty line + if [ "${stripped_line:0:1}" != "#" ]; then + # Non-comment line + # Split on "=" character and escape double quotes used for command arguments + name=$(trim "${stripped_line%%\=*}") + cmds=$(trim "${${stripped_line#*\=}//\"/\\\"}") + + alias ",$name"="send_commands \"$cmds\"" + fi + fi +done <*.ftcommands +popd > /dev/null + + +# Termlet-related logic +function run_termlet() { + if [ -t 1 ]; then + /usr/bin/zsh "@PKGDATADIR@/Termlets/$@" + else + /usr/bin/zsh "$@" + fi +} + +# Set up termlet aliases +pushd "@PKGDATADIR@/Termlets" > /dev/null +for filename in *; do + alias $filename="run_termlet '$filename'" +done +popd > /dev/null + +cd ~ diff --git a/data/Termlets/ls b/data/Termlets/ls index 5242d7a..5bc83f7 100755 --- a/data/Termlets/ls +++ b/data/Termlets/ls @@ -1,5 +1,12 @@ #!/bin/bash +if [ -n "$ZSH_VERSION" ]; then + autoload text_menu_start + autoload text_menu_end + autoload final_term_control_sequence + autoload send_control_sequence +fi + ls_output=$(ls "$@") dir_begin_mark=$(text_menu_start '2') dir_end_mark=$(text_menu_end '2') diff --git a/data/Termlets/ps b/data/Termlets/ps index 8ef536d..3ca13ad 100755 --- a/data/Termlets/ps +++ b/data/Termlets/ps @@ -1,5 +1,12 @@ #!/bin/bash +if [ -n "$ZSH_VERSION" ]; then + autoload text_menu_start + autoload text_menu_end + autoload final_term_control_sequence + autoload send_control_sequence +fi + # IFS is '\n' IFS=$'\012' psoutput=($(ps "$@")) diff --git a/data/Termlets/wget b/data/Termlets/wget index e424376..f67f026 100755 --- a/data/Termlets/wget +++ b/data/Termlets/wget @@ -1,5 +1,11 @@ #!/bin/bash +if [ -n "$ZSH_VERSION" ]; then + autoload send_progress + autoload send_control_sequence + autoload final_term_control_sequenc +fi + # TODO: Multiple file downloads? # Note that wget writes its output to STDERR instead of STDOUT diff --git a/data/org.gnome.finalterm.gschema.xml b/data/org.gnome.finalterm.gschema.xml index 63839cc..5de5e3d 100644 --- a/data/org.gnome.finalterm.gschema.xml +++ b/data/org.gnome.finalterm.gschema.xml @@ -46,7 +46,7 @@ '/bin/bash' - Path to the shell executable which is to be run (NOTE: Only bash is currently supported) + Path to the shell executable which is to be run (NOTE: Only bash and zsh are currently supported) diff --git a/src/Terminal.vala b/src/Terminal.vala index c9e1eb5..c7dbaae 100644 --- a/src/Terminal.vala +++ b/src/Terminal.vala @@ -242,8 +242,27 @@ public class Terminal : Object { private void run_shell() { Environment.set_variable("TERM", Settings.get_default().emulated_terminal, true); - string[] arguments = { Settings.get_default().shell_path, "--rcfile", - Config.PKGDATADIR + "/Startup/bash_startup", "-i" }; + string shell_basename = Filename.display_basename(Settings.get_default().shell_path); + string[] valid_shells = { "zsh", "bash" }; + + if (!(shell_basename in valid_shells)){ + critical(_("shell_path not supported, only bash and zsh are currently supported")); + Posix.exit(Posix.EXIT_FAILURE); + } + + string shell_include = Config.PKGDATADIR + "/Startup/" + shell_basename + "_startup"; + + string[] arguments = {}; + switch(shell_basename){ + case "bash": + arguments = { Settings.get_default().shell_path, "--rcfile", shell_include, "-i" }; + break; + case "zsh": + //arguments = { Settings.get_default().shell_path, shell_include, "-i" }; + Environment.set_variable("FINALTERMSCRIPT", shell_include, true); + arguments = { Settings.get_default().shell_path, "-i" }; + break; + } // Add custom shell arguments foreach (var argument in Settings.get_default().shell_arguments) { From 2eea4847ba5d0c359fdec19f19f2abc9c47bf5f3 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Sat, 13 Sep 2014 00:03:58 -0400 Subject: [PATCH 2/8] zsh fixes for ps and wget Termlets - wget - bottom of terminal is now updated when Download is initiated, and progress is updated, but it is not cleared when download finished/canceled - ps - menus actually show up now --- data/Termlets/ps | 21 ++++++++++++++++----- data/Termlets/wget | 3 ++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/data/Termlets/ps b/data/Termlets/ps index 3ca13ad..0af29ea 100755 --- a/data/Termlets/ps +++ b/data/Termlets/ps @@ -10,16 +10,27 @@ fi # IFS is '\n' IFS=$'\012' psoutput=($(ps "$@")) + +headeridx=0 +while [ -z ${psoutput[$headeridx]+x} ]; do + ((headeridx+=1)) +done + # Just in case PPID could be displayed before PID, search for ' PID' -pid_index=$(awk -v a="${psoutput[0]}" -v b=' PID' 'BEGIN{print index(a,b)}') +pid_index=$(awk -v a="${psoutput[$headeridx]}" -v b=' PID' 'BEGIN{print index(a,b)}') # don’t use $() in loops, as it spawns a sub-process, so get markings out of the loop. begin_mark=$(text_menu_start '3') end_mark=$(text_menu_end '3') # last character position of PID display. let pid_end=pid_index+4 -# display 1st line as is, then destroy it. -echo -e ${psoutput[0]} -unset psoutput[0] + +# display header line as is and destroy it +echo -e ${psoutput[$headeridx]} +if [ -n "$ZSH_VERSION" ]; then + psoutput[$headeridx]=() +else + unset psoutput[$headeridx] +fi for line in ${psoutput[@]}; do # Content line @@ -27,7 +38,7 @@ for line in ${psoutput[@]}; do pid_part=${left_part##* } # Remove pid_part from left_part left_part=${left_part:0:${#left_part}-${#pid_part}} - right_part=${line:pid_end-1} + right_part=${line:${pid_end}-1} modified_line="$left_part$begin_mark$pid_part$end_mark$right_part" echo -e "$modified_line" done diff --git a/data/Termlets/wget b/data/Termlets/wget index f67f026..6836735 100755 --- a/data/Termlets/wget +++ b/data/Termlets/wget @@ -3,7 +3,8 @@ if [ -n "$ZSH_VERSION" ]; then autoload send_progress autoload send_control_sequence - autoload final_term_control_sequenc + autoload final_term_control_sequence + setopt BASH_REMATCH fi # TODO: Multiple file downloads? From 98f1eef8a6f5a82e0e03f5541d51aa731aba1ac7 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Sat, 13 Sep 2014 21:51:45 -0400 Subject: [PATCH 3/8] use SHELL env variable to determine shell to launch, fallback to default setting (bash) if SHELL is not supported --- src/Terminal.vala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Terminal.vala b/src/Terminal.vala index c7dbaae..5b043a0 100644 --- a/src/Terminal.vala +++ b/src/Terminal.vala @@ -242,12 +242,15 @@ public class Terminal : Object { private void run_shell() { Environment.set_variable("TERM", Settings.get_default().emulated_terminal, true); - string shell_basename = Filename.display_basename(Settings.get_default().shell_path); + string shell = Environment.get_variable("SHELL") ?? Settings.get_default().shell_path; + string shell_basename = Filename.display_basename(shell); + string[] valid_shells = { "zsh", "bash" }; if (!(shell_basename in valid_shells)){ - critical(_("shell_path not supported, only bash and zsh are currently supported")); - Posix.exit(Posix.EXIT_FAILURE); + message(_("shell defined in environment is not supported, falling back to bash")); + shell = Settings.get_default().shell_path; + shell_basename = Filename.display_basename(shell); } string shell_include = Config.PKGDATADIR + "/Startup/" + shell_basename + "_startup"; @@ -255,12 +258,11 @@ public class Terminal : Object { string[] arguments = {}; switch(shell_basename){ case "bash": - arguments = { Settings.get_default().shell_path, "--rcfile", shell_include, "-i" }; + arguments = { shell, "--rcfile", shell_include, "-i" }; break; case "zsh": - //arguments = { Settings.get_default().shell_path, shell_include, "-i" }; Environment.set_variable("FINALTERMSCRIPT", shell_include, true); - arguments = { Settings.get_default().shell_path, "-i" }; + arguments = { shell, "-i" }; break; } @@ -270,7 +272,7 @@ public class Terminal : Object { } // Replace child process with shell process - Posix.execvp(Settings.get_default().shell_path, arguments); + Posix.execvp(shell, arguments); // If this line is reached, execvp() must have failed critical(_("execvp failed")); From 5a9ea24070a93766fc6ba646cb9d55de8f4eb85a Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Sun, 14 Sep 2014 09:40:06 -0400 Subject: [PATCH 4/8] cleanup variable naming for trim command --- data/Startup/zsh_startup.in | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in index 2363dbc..b5605c3 100644 --- a/data/Startup/zsh_startup.in +++ b/data/Startup/zsh_startup.in @@ -11,6 +11,7 @@ export FPATH autoload send_control_sequence autoload final_term_control_sequence + # Logic for prompt and command detection function send_return_code() { # Send sequence containing the return code of the last command @@ -35,10 +36,10 @@ PS1=$PS1$(final_term_control_sequence 'B') # Logic for terminal commands function trim() { - local var=$1 - var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters - var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters - echo -n "$var" + local text=$1 + text="${text#"${text%%[![:space:]]*}"}" # remove leading whitespace characters + text="${text%"${text##*[![:space:]]}"}" # remove trailing whitespace characters + echo -n "$text" } function send_commands() { From d4a68e75bec62f9f8935243182229101506689e4 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Tue, 16 Sep 2014 22:14:55 -0400 Subject: [PATCH 5/8] separate zsh termlets from bash termlets, do not hardcode shell paths --- CMakeLists.txt | 21 +++++++++--- README-ZSH.md | 2 +- data/Startup/bash_startup.in | 6 ++-- .../Startup/{preexec.bash => preexec.bash.in} | 2 +- data/Startup/zsh_startup.in | 8 ++--- data/Termlets/bash/ls.in | 33 +++++++++++++++++++ data/Termlets/bash/ps.in | 33 +++++++++++++++++++ data/Termlets/bash/wget.in | 30 +++++++++++++++++ data/Termlets/{ls => zsh/ls.in} | 12 +++---- data/Termlets/{ps => zsh/ps.in} | 18 ++++------ data/Termlets/{wget => zsh/wget.in} | 14 ++++---- 11 files changed, 139 insertions(+), 40 deletions(-) rename data/Startup/{preexec.bash => preexec.bash.in} (99%) create mode 100755 data/Termlets/bash/ls.in create mode 100755 data/Termlets/bash/ps.in create mode 100755 data/Termlets/bash/wget.in rename data/Termlets/{ls => zsh/ls.in} (84%) rename data/Termlets/{ps => zsh/ps.in} (76%) rename data/Termlets/{wget => zsh/wget.in} (80%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 588a1ca..5dc82c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,24 @@ if (NOT MINIMAL_FLAGS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb") endif (NOT MINIMAL_FLAGS) +find_program(BASHEXE bash /bin /usr/bin /usr/local/bin) +if(NOT DEFINED BASHEXE) + set(BASHEXE "/bin/bash") +endif(NOT DEFINED BASHEXE) configure_file(${CMAKE_SOURCE_DIR}/data/Startup/bash_startup.in ${CMAKE_BINARY_DIR}/Startup/bash_startup @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Startup/preexec.bash.in ${CMAKE_BINARY_DIR}/Startup/preexec.bash @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/bash/ps.in ${CMAKE_BINARY_DIR}/Termlets/bash/ps @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/bash/ls.in ${CMAKE_BINARY_DIR}/Termlets/bash/ls @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/bash/wget.in ${CMAKE_BINARY_DIR}/Termlets/bash/wget @ONLY) + +find_program(ZSHEXE zsh /bin /usr/bin /usr/local/bin) +if(NOT DEFINED ZSHEXE) + set(ZSHEXE "/usr/bin/zsh") +endif(NOT DEFINED ZSHEXE) configure_file(${CMAKE_SOURCE_DIR}/data/Startup/zsh_startup.in ${CMAKE_BINARY_DIR}/Startup/zsh_startup @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/zsh/ps.in ${CMAKE_BINARY_DIR}/Termlets/zsh/ps @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/zsh/ls.in ${CMAKE_BINARY_DIR}/Termlets/zsh/ls @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/data/Termlets/zsh/wget.in ${CMAKE_BINARY_DIR}/Termlets/zsh/wget @ONLY) set(PKGS clutter-gtk-1.0 mx-1.0 keybinder-3.0 gee-0.8) @@ -104,12 +120,9 @@ install(TARGETS finalterm RUNTIME DESTINATION bin) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ColorSchemes DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/KeyBindings DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_BINARY_DIR}/Startup DESTINATION share/finalterm) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/data/Startup/preexec.bash DESTINATION share/finalterm/Startup) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Startup/zsh_functions DESTINATION share/finalterm/Startup) -file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/zsh_functions/*") -install(FILES ${files} DESTINATION share/finalterm/Stertup/zsh_functions) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/TerminalCommands DESTINATION share/finalterm) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Termlets DESTINATION share/finalterm FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY ${CMAKE_BINARY_DIR}/Termlets DESTINATION share/finalterm FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/TextMenus DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Themes DESTINATION share/finalterm) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/Icons/hicolor DESTINATION share/icons) diff --git a/README-ZSH.md b/README-ZSH.md index c261cef..f83be09 100644 --- a/README-ZSH.md +++ b/README-ZSH.md @@ -1,5 +1,5 @@ Requirements -- gsettings set org.gnome.finalterm shell-path /usr/bin/zsh +- $SHELL set to zsh - add the following to ~/.zshrc: ``` if [ -n "$FINALTERMSCRIPT" ]; then diff --git a/data/Startup/bash_startup.in b/data/Startup/bash_startup.in index ff118eb..d8963fa 100644 --- a/data/Startup/bash_startup.in +++ b/data/Startup/bash_startup.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!@BASHEXE@ # Include default startup file so that user's settings are respected [[ -r ~/.bashrc ]] && source ~/.bashrc @@ -112,14 +112,14 @@ export -f send_progress function run_termlet() { if [ -t 1 ]; then - "@PKGDATADIR@/Termlets/$@" + "@PKGDATADIR@/Termlets/bash/$@" else "$@" fi } # Set up termlet aliases -pushd "@PKGDATADIR@/Termlets" > /dev/null +pushd "@PKGDATADIR@/Termlets/bash" > /dev/null for filename in *; do alias $filename="run_termlet '$filename'" done diff --git a/data/Startup/preexec.bash b/data/Startup/preexec.bash.in similarity index 99% rename from data/Startup/preexec.bash rename to data/Startup/preexec.bash.in index 3057aee..3545ae9 100644 --- a/data/Startup/preexec.bash +++ b/data/Startup/preexec.bash.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!@BASHEXE@ # NOTE: This file is taken (with minor modifications) from # Glyph Lefkowitz's blog post "This bash shell is now fully operational!" diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in index b5605c3..a10885b 100644 --- a/data/Startup/zsh_startup.in +++ b/data/Startup/zsh_startup.in @@ -1,4 +1,4 @@ -#!/usr/bin/zsh +#!@ZSHEXE@ # Final Term's customizations start here finalterm_fpath="@PKGDATADIR@/Startup/zsh_functions" @@ -68,14 +68,14 @@ popd > /dev/null # Termlet-related logic function run_termlet() { if [ -t 1 ]; then - /usr/bin/zsh "@PKGDATADIR@/Termlets/$@" + "@PKGDATADIR@/Termlets/zsh/$@" else - /usr/bin/zsh "$@" + "$@" fi } # Set up termlet aliases -pushd "@PKGDATADIR@/Termlets" > /dev/null +pushd "@PKGDATADIR@/Termlets/zsh" > /dev/null for filename in *; do alias $filename="run_termlet '$filename'" done diff --git a/data/Termlets/bash/ls.in b/data/Termlets/bash/ls.in new file mode 100755 index 0000000..a3731a8 --- /dev/null +++ b/data/Termlets/bash/ls.in @@ -0,0 +1,33 @@ +#!@BASHEXE@ + +ls_output=$(ls "$@") +dir_begin_mark=$(text_menu_start '2') +dir_end_mark=$(text_menu_end '2') +file_begin_mark=$(text_menu_start '1') +file_end_mark=$(text_menu_end '1') + +# Surround with additional newlines to facilitate matching (see below) +ls_output=$'\n'$ls_output$'\n' + +# TODO: Search for files in directory passed to ls rather than the current directory +for filename in *; do + if [[ -d $filename ]]; then + file_substitution="$dir_begin_mark$filename$dir_end_mark" + else + file_substitution="$file_begin_mark$filename$file_end_mark" + fi + + # Short format ("ls"; each filename on a single line) + ls_output=${ls_output/$'\n'$filename$'\n'/$'\n'$file_substitution$'\n'} + # Long format ("ls -l") + ls_output=${ls_output/ $filename$'\n'/ $file_substitution$'\n'} + # Long format; symlinks + ls_output=${ls_output/ $filename ->/ $file_substitution ->} +done + +# Strip leading newline +ls_output=${ls_output#$'\n'} +# Strip trailing newline +ls_output=${ls_output%$'\n'} + +echo -e "$ls_output" diff --git a/data/Termlets/bash/ps.in b/data/Termlets/bash/ps.in new file mode 100755 index 0000000..f8bbb6f --- /dev/null +++ b/data/Termlets/bash/ps.in @@ -0,0 +1,33 @@ +#!@BASHEXE@ + +# IFS is '\n' +IFS=$'\012' +psoutput=($(ps "$@")) + +headeridx=0 +while [ -z ${psoutput[$headeridx]+x} ]; do + ((headeridx+=1)) +done + +# Just in case PPID could be displayed before PID, search for ' PID' +pid_index=$(awk -v a="${psoutput[$headeridx]}" -v b=' PID' 'BEGIN{print index(a,b)}') +# don’t use $() in loops, as it spawns a sub-process, so get markings out of the loop. +begin_mark=$(text_menu_start '3') +end_mark=$(text_menu_end '3') +# last character position of PID display. +let pid_end=pid_index+4 + +# display header line as is and destroy it +echo -e ${psoutput[$headeridx]} +unset psoutput[$headeridx] + +for line in ${psoutput[@]}; do + # Content line + left_part=${line:0:${pid_end}-1} + pid_part=${left_part##* } + # Remove pid_part from left_part + left_part=${left_part:0:${#left_part}-${#pid_part}} + right_part=${line:${pid_end}-1} + modified_line="$left_part$begin_mark$pid_part$end_mark$right_part" + echo -e "$modified_line" +done diff --git a/data/Termlets/bash/wget.in b/data/Termlets/bash/wget.in new file mode 100755 index 0000000..14fd27b --- /dev/null +++ b/data/Termlets/bash/wget.in @@ -0,0 +1,30 @@ +#!@BASHEXE@ + +# TODO: Multiple file downloads? + +# Note that wget writes its output to STDERR instead of STDOUT +wget --progress=bar:force "$@" 2>&1 | + +while IFS= read -r line; do + echo "$line" + + if [[ $line == "" ]]; then + # Progress bar reached + # => Switch to CR as line separator to receive + # individual progress bar updates + while IFS= read -r -d $'\r' line; do + # Extract current progress percentage + if [[ $line =~ ^\ ?([0-9]{1,3})% ]]; then + send_progress "${BASH_REMATCH[1]}" "Downloading $1..." + fi + + echo -ne "\r$line" + done + + # Process completed + send_progress "-1" "" + + # Print remaining output + echo -ne "\r$line" + fi +done diff --git a/data/Termlets/ls b/data/Termlets/zsh/ls.in similarity index 84% rename from data/Termlets/ls rename to data/Termlets/zsh/ls.in index 5bc83f7..68c91b8 100755 --- a/data/Termlets/ls +++ b/data/Termlets/zsh/ls.in @@ -1,11 +1,9 @@ -#!/bin/bash +#!@ZSHEXE@ -if [ -n "$ZSH_VERSION" ]; then - autoload text_menu_start - autoload text_menu_end - autoload final_term_control_sequence - autoload send_control_sequence -fi +autoload text_menu_start +autoload text_menu_end +autoload final_term_control_sequence +autoload send_control_sequence ls_output=$(ls "$@") dir_begin_mark=$(text_menu_start '2') diff --git a/data/Termlets/ps b/data/Termlets/zsh/ps.in similarity index 76% rename from data/Termlets/ps rename to data/Termlets/zsh/ps.in index 0af29ea..6b19441 100755 --- a/data/Termlets/ps +++ b/data/Termlets/zsh/ps.in @@ -1,11 +1,9 @@ -#!/bin/bash +#!@ZSHEXE@ -if [ -n "$ZSH_VERSION" ]; then - autoload text_menu_start - autoload text_menu_end - autoload final_term_control_sequence - autoload send_control_sequence -fi +autoload text_menu_start +autoload text_menu_end +autoload final_term_control_sequence +autoload send_control_sequence # IFS is '\n' IFS=$'\012' @@ -26,11 +24,7 @@ let pid_end=pid_index+4 # display header line as is and destroy it echo -e ${psoutput[$headeridx]} -if [ -n "$ZSH_VERSION" ]; then - psoutput[$headeridx]=() -else - unset psoutput[$headeridx] -fi +psoutput[$headeridx]=() for line in ${psoutput[@]}; do # Content line diff --git a/data/Termlets/wget b/data/Termlets/zsh/wget.in similarity index 80% rename from data/Termlets/wget rename to data/Termlets/zsh/wget.in index 6836735..f64988d 100755 --- a/data/Termlets/wget +++ b/data/Termlets/zsh/wget.in @@ -1,11 +1,9 @@ -#!/bin/bash - -if [ -n "$ZSH_VERSION" ]; then - autoload send_progress - autoload send_control_sequence - autoload final_term_control_sequence - setopt BASH_REMATCH -fi +#!@ZSHEXE@ + +autoload send_progress +autoload send_control_sequence +autoload final_term_control_sequence +setopt BASH_REMATCH # TODO: Multiple file downloads? From 44eb025ec30ac1ecdafbcf57510feef9e9927f3c Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Wed, 17 Sep 2014 01:50:18 -0400 Subject: [PATCH 6/8] improve zsh handling of control sequences --- .../zsh_functions/send_control_sequence | 1 + data/Startup/zsh_startup.in | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/data/Startup/zsh_functions/send_control_sequence b/data/Startup/zsh_functions/send_control_sequence index e10a5d5..eac83bd 100644 --- a/data/Startup/zsh_functions/send_control_sequence +++ b/data/Startup/zsh_functions/send_control_sequence @@ -1 +1,2 @@ +setopt no_prompt_cr echo -ne "$1" diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in index a10885b..e4b5223 100644 --- a/data/Startup/zsh_startup.in +++ b/data/Startup/zsh_startup.in @@ -13,22 +13,22 @@ autoload final_term_control_sequence # Logic for prompt and command detection -function send_return_code() { +send_return_code() { # Send sequence containing the return code of the last command send_control_sequence "$(final_term_control_sequence 'D' "$?")" } -precmd() { - # Send sequence marking a command prompt - send_control_sequence "$(final_term_control_sequence 'A')" +precmd_hook() { + # Send sequence marking a command prompt + send_control_sequence "$(final_term_control_sequence 'A')" } +precmd_functions=( send_return_code precmd_hook $preexec_functions ) -preexec() { - # Send sequence containing the command to be executed - send_control_sequence "$(final_term_control_sequence 'C' "$1")" +preexec_hook() { + # Send sequence containing the command to be executed + send_control_sequence "$(final_term_control_sequence 'C' "$1")" } - -PROMPT_COMMAND=send_return_code;$PROMPT_COMMAND +preexec_functions=( preexec_hook $preexec_functions ) # Send sequence marking the start of a command PS1=$PS1$(final_term_control_sequence 'B') From 76ce1a02a65546edbdaef2306734a8bbc6d6a75b Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Fri, 3 Oct 2014 17:21:25 -0400 Subject: [PATCH 7/8] Fix crash when attempting to display popup menu zsh still does not invoke the menu, but at least now it doesn't crash on no input --- data/Startup/zsh_startup.in | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in index e4b5223..8dff374 100644 --- a/data/Startup/zsh_startup.in +++ b/data/Startup/zsh_startup.in @@ -26,14 +26,11 @@ precmd_functions=( send_return_code precmd_hook $preexec_functions ) preexec_hook() { # Send sequence containing the command to be executed + send_control_sequence "$(final_term_control_sequence 'B')" send_control_sequence "$(final_term_control_sequence 'C' "$1")" } preexec_functions=( preexec_hook $preexec_functions ) -# Send sequence marking the start of a command -PS1=$PS1$(final_term_control_sequence 'B') - - # Logic for terminal commands function trim() { local text=$1 From f9153f38323da0d8ae4fce916042b6c0381c2420 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Fri, 3 Oct 2014 22:17:11 -0400 Subject: [PATCH 8/8] some additional debugging and attempts to cleanup crash issue with zsh and an empty command. Looks like the cursor_position is < command_start_postion (at least with my .zshrc) --- data/Startup/zsh_startup.in | 7 ++++--- src/Command.vala | 25 +++++++++++++++---------- src/TerminalOutput.vala | 6 +++++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/data/Startup/zsh_startup.in b/data/Startup/zsh_startup.in index 8dff374..5219d03 100644 --- a/data/Startup/zsh_startup.in +++ b/data/Startup/zsh_startup.in @@ -22,14 +22,15 @@ precmd_hook() { # Send sequence marking a command prompt send_control_sequence "$(final_term_control_sequence 'A')" } -precmd_functions=( send_return_code precmd_hook $preexec_functions ) +precmd_functions=( send_return_code $preexec_functions precmd_hook ) preexec_hook() { # Send sequence containing the command to be executed - send_control_sequence "$(final_term_control_sequence 'B')" send_control_sequence "$(final_term_control_sequence 'C' "$1")" } -preexec_functions=( preexec_hook $preexec_functions ) +preexec_functions=( $preexec_functions preexec_hook ) + +PROMPT="${PROMPT}$(final_term_control_sequence 'B')" # Logic for terminal commands function trim() { diff --git a/src/Command.vala b/src/Command.vala index 2fcf14f..123a40b 100644 --- a/src/Command.vala +++ b/src/Command.vala @@ -97,17 +97,22 @@ public class Command : Object { foreach (var parameter in parameters) { var substitute_parameter = parameter; - // Replace placeholder "%i" with placeholder_substitutes[i - 1] - for (int i = 0; i < placeholder_substitutes.size; i++) { - substitute_parameter = substitute_parameter.replace( - "%" + (i + 1).to_string(), - placeholder_substitutes.get(i)); + try { + // Replace placeholder "%i" with placeholder_substitutes[i - 1] + for (int i = 0; i < placeholder_substitutes.size; i++) { + substitute_parameter = substitute_parameter.replace( + "%" + (i + 1).to_string(), + placeholder_substitutes.get(i)); + message(_("placeholder_substitute: %s"), placeholder_substitutes.get(i)); + } + + // Remove remaining placeholders + substitute_parameter = placeholder_pattern.replace(substitute_parameter, + -1, 0, ""); + substitute_command.parameters.add(substitute_parameter); + } catch (GLib.RegexError e) { + error(_("Error substituting parameter. placeholder_pattern: %s substitute_parameter: %s, exception: %s"), placeholder_pattern.get_pattern, substitute_parameter, e.message); } - - // Remove remaining placeholders - substitute_parameter = placeholder_pattern.replace(substitute_parameter, -1, 0, ""); - - substitute_command.parameters.add(substitute_parameter); } substitute_command.execute(); diff --git a/src/TerminalOutput.vala b/src/TerminalOutput.vala index 93cde65..3d67938 100644 --- a/src/TerminalOutput.vala +++ b/src/TerminalOutput.vala @@ -383,7 +383,7 @@ public class TerminalOutput : Gee.ArrayList { warning(_("Command start control sequence received while already in command mode")); command_mode = true; command_start_position = cursor_position; - message(_("Command mode entered")); + message(_("Command mode entered, cursor_position (line: %i column: %i)"), cursor_position.line, cursor_position.column); break; case TerminalStream.StreamElement.ControlSequenceType.FTCS_COMMAND_EXECUTED: @@ -550,8 +550,12 @@ public class TerminalOutput : Gee.ArrayList { public string get_command() { // TODO: Revisit this check (condition should never fail) if (command_start_position.compare(cursor_position) < 0) { + warning(_("command_start_position: (%i,%i) cursor_postion: (%i,%i)"), + command_start_position.line, command_start_position.column, + cursor_position.line, cursor_position.column); return get_range(command_start_position, cursor_position); } else { + warning(_("cursor_position < command_start_position")); return ""; } }