diff --git a/documentation/man/features/kw-explore.rst b/documentation/man/features/kw-explore.rst index 5138c3bce..0f5a28aa6 100644 --- a/documentation/man/features/kw-explore.rst +++ b/documentation/man/features/kw-explore.rst @@ -7,7 +7,7 @@ kw-explore - Explore folder SYNOPSIS ======== *kw* (*e* | *explore*) [(-l | \--log) | (-g | \--grep) | (-a | \--all) | \--verbose] - [(-c | \--only-source) | (-H | \--only-header)] + [(-c | \--only-source) | (-H | \--only-header)] [(-C[] | \--show-context[=])] [-p] [ | ] DESCRIPTION @@ -45,5 +45,23 @@ OPTIONS -H | \--only-header: With this option, it is possible to show only the results from the header. +-C[] | \--show-context[=]: + Show lines of additional context above and below the matched line. + If is not specified, the default value of 3 will be used. + \--verbose: Verbose mode allows the user to see the commands executed under the hood. + +EXAMPLES +======== +To show matched line with context using long-form flag:: + + kw explore --show-context=5 search_string + +To show matched line with context using short flag:: + + kw explore -C5 search_string + +Search through all tracked and untracked files, default value of 3 will be used for context:: + + kw explore -C --all search_string diff --git a/src/_kw b/src/_kw index e5079cedb..c6caf4420 100644 --- a/src/_kw +++ b/src/_kw @@ -282,6 +282,7 @@ _kw_explore() '(-a --all -l --log -g --grep)'{-a,--all}'[search for all matches in files under or not git management]' \ '(-c --only-source -H --only-header)'{-c,--only-source}'[show only results from the source]' \ '(-H --only-header -c --only-source)'{-H,--only-header}'[show only results from the header]' \ + '(-C --show-context)'{-C-,--show-context=-}'[set the context value]' \ '1: : ' \ '2: :_files' } diff --git a/src/bash_autocomplete.sh b/src/bash_autocomplete.sh index 4fb435e81..1159b8ccd 100644 --- a/src/bash_autocomplete.sh +++ b/src/bash_autocomplete.sh @@ -53,7 +53,7 @@ function _kw_autocomplete() kw_options['remote']='--add --remove --rename --list --global --set-default --verbose' - kw_options['explore']='--log --grep --all --only-header --only-source --exactly --verbose' + kw_options['explore']='--log --grep --all --only-header --only-source --exactly --show-context --verbose' kw_options['e']="${kw_options['explore']}" kw_options['pomodoro']='--set-timer --check-timer --show-tags --tag --description --help --verbose' diff --git a/src/explore.sh b/src/explore.sh index c1f88f604..c69c2d751 100644 --- a/src/explore.sh +++ b/src/explore.sh @@ -15,6 +15,7 @@ function explore_main() local flag local search local path + local context local ret if [[ "$1" =~ -h|--help ]]; then @@ -31,6 +32,7 @@ function explore_main() flag="${options_values['TEST_MODE']:-'SILENT'}" search="${options_values['SEARCH']}" path="${options_values['PATH']:-'.'}" + context="${options_values['CONTEXT']:-0}" [[ -n "${options_values['VERBOSE']}" ]] && flag='VERBOSE' @@ -48,19 +50,19 @@ function explore_main() if [[ "${options_values['TYPE']}" -eq 2 ]]; then # Use GNU GREP - explore_files_gnu_grep "$search" "$path" "$flag" + explore_files_gnu_grep "$search" "$path" "$context" "$flag" return fi if [[ "${options_values['TYPE']}" -eq 3 ]]; then # Search in directories controlled or not by git - explore_all_files_git "$search" "$path" "$flag" + explore_all_files_git "$search" "$path" "$context" "$flag" return fi if [[ -z "${options_values['TYPE']}" ]]; then # Search in files under git control - explore_files_under_git "$search" "$path" "$flag" + explore_files_under_git "$search" "$path" "$context" "$flag" return fi } @@ -75,8 +77,8 @@ function explore_main() # This function also set options_values function parse_explore_options() { - local long_options='log,grep,all,only-header,only-source,exactly,verbose' - local short_options='l,g,a,H,c' + local long_options='log,grep,all,only-header,only-source,exactly,verbose,show-context::' + local short_options='l,g,a,H,c,C::' local options if [[ "$#" -eq 0 ]]; then @@ -97,6 +99,7 @@ function parse_explore_options() options_values['TYPE']='' options_values['SCOPE']='' options_values['EXACTLY']='' + options_values['CONTEXT']='' options_values['VERBOSE']='' eval "set -- $options" @@ -158,6 +161,24 @@ function parse_explore_options() ;; --verbose) options_values['VERBOSE']=1 + shift + ;; + --show-context | -C) + if [[ -n "${options_values['CONTEXT']}" ]]; then + options_values['ERROR']='Invalid arguments: Multiple context values!' + return 22 # EINVAL + fi + + if [[ ! "$2" ]]; then + options_values['CONTEXT']=3 + elif [[ ! "$2" =~ ^[0-9]+$ ]]; then + options_values['ERROR']='Context value must be a non-negative integer!' + return 22 # EINVAL + else + options_values['CONTEXT']="$2" + shift + fi + shift ;; --) # End of options, beginning of arguments @@ -196,7 +217,7 @@ function explore_git_log() flag=${flag:-'SILENT'} - cmd_manager "$flag" "git log --grep='$search_string' $path" + cmd_manager "$flag" "git log --grep='${search_string}' ${path}" } # This function searches string in files under git control. @@ -209,11 +230,12 @@ function explore_files_under_git() { local regex="$1" local path="$2" - local flag="$3" + local context="$3" + local flag="$4" flag=${flag:-'SILENT'} - cmd_manager "$flag" "git grep -e '$regex' -nI $path" + cmd_manager "$flag" "git grep --context ${context} -e '${regex}' --line-number -I ${path}" } # This function uses git grep tool to search string in files under or not git @@ -228,11 +250,12 @@ function explore_all_files_git() { local regex="$1" local path="$2" - local flag="$3" + local context="$3" + local flag="$4" flag=${flag:-'SILENT'} - cmd_manager "$flag" "git grep --no-index -e '$regex' -nI $path" + cmd_manager "$flag" "git grep --no-index --context ${context} -e '${regex}' --line-number -I ${path}" } # This function allows the use of gnu grep utility to manages the search for @@ -246,11 +269,12 @@ function explore_files_gnu_grep() { local regex="$1" local path="$2" - local flag="$3" + local context="$3" + local flag="$4" flag=${flag:-'SILENT'} - cmd_manager "$flag" "grep --color -nrI $path -e '$regex'" + cmd_manager "$flag" "grep --color --line-number --recursive -I ${path} --context ${context} -e '${regex}'" } function explore_help() @@ -267,5 +291,6 @@ function explore_help() ' explore,e --all,-a - Search for all match under or not of git management' \ ' explore,e --only-source,-c - Search for all in source files' \ ' explore,e --only-header,-H - Search for all in header files' \ + ' explore,e --show-context[=],-C[] - Print lines of output context (default: 3)' \ ' explore,e --verbose - Show a detailed output' } diff --git a/tests/unit/explore_test.sh b/tests/unit/explore_test.sh index 298fdf574..1f3555ec9 100755 --- a/tests/unit/explore_test.sh +++ b/tests/unit/explore_test.sh @@ -67,9 +67,14 @@ function test_explore_files_under_git_repo() assertEquals "($LINENO)" "$MSG_OUT" "$output" output=$(explore_main 'GNU grep' '.' 'TEST_MODE') - expected_result="git grep -e 'GNU grep' -nI ." + expected_result="git grep --context 0 -e 'GNU grep' --line-number -I ." assertEquals "($LINENO)" "$expected_result" "$output" + # Test for non zero context value + output=$(explore_main --show-context=5 'GNU grep' '.' 'TEST_MODE') + expected_result="git grep --context 5 -e 'GNU grep' --line-number -I ." + assertEquals "(${LINENO})" "$expected_result" "$output" + # Test if search only in files under git control cp "$current_path/tests/unit/samples/grep_check.c" ./ MSG_OUT='GNU grep' @@ -82,13 +87,13 @@ function test_explore_files_under_git_repo() # Test only-source and only-header MSG_OUT='3' - output=$(explore_main 'camelCase' | wc -l) + output=$(explore_main 'camelCase' | wc --lines) assertEquals "($LINENO)" "$MSG_OUT" "$output" MSG_OUT='2' - output=$(explore_main -c 'camelCase' | wc -l) + output=$(explore_main -c 'camelCase' | wc --lines) assertEquals "($LINENO)" "$MSG_OUT" "$output" MSG_OUT='1' - output=$(explore_main -H 'camelCase' | wc -l) + output=$(explore_main -H 'camelCase' | wc --lines) assertEquals "($LINENO)" "$MSG_OUT" "$output" cd "$current_path" || { @@ -133,7 +138,11 @@ function test_explore_grep() assertEquals "($LINENO)" '.git' "$output" output=$(explore_main --grep 'GNU grep' '.' 'TEST_MODE') - expected_result="grep --color -nrI . -e 'GNU grep'" + expected_result="grep --color --line-number --recursive -I . --context 0 -e 'GNU grep'" + assertEquals "(${LINENO})" "$expected_result" "$output" + + output=$(explore_main --grep --show-context=5 'GNU grep' '.' 'TEST_MODE') + expected_result="grep --color --line-number --recursive -I . --context 5 -e 'GNU grep'" assertEquals "($LINENO)" "$expected_result" "$output" cd "$current_path" || { @@ -153,7 +162,11 @@ function test_explore_git() } output=$(explore_main --all 'GNU grep' '.' 'TEST_MODE') - expected_result="git grep --no-index -e 'GNU grep' -nI ." + expected_result="git grep --no-index --context 0 -e 'GNU grep' --line-number -I ." + assertEquals "(${LINENO})" "$expected_result" "$output" + + output=$(explore_main --all --show-context=5 'GNU grep' '.' 'TEST_MODE') + expected_result="git grep --no-index --context 5 -e 'GNU grep' --line-number -I ." assertEquals "($LINENO)" "$expected_result" "$output" # Test if the search ignores files in .git @@ -172,6 +185,47 @@ function test_explore_git() } } +function test_explore_context() +{ + local -r current_path="$PWD" + local expected_context='3' + local expected_match='avoid' + local msg_out='7' + + cd "$SHUNIT_TMPDIR" || { + fail "(${LINENO}) It was not possible to move to temporary directory" + return + } + + # Check the number of output lines + output=$(explore_main --show-context=3 'avoid' codestyle_error.c | wc --lines) + assertEquals "(${LINENO})" "$msg_out" "$output" + + output=$(explore_main --show-context="$expected_context" "$expected_match" codestyle_error.c) + + # Check if the expected match and context lines are present in the output + assert_substring_match 'Expected match not found!' "${LINENO}" "${expected_match}" "$output" + + # Check context lines below the match + for ((i = 1; i <= expected_context; i++)); do + CONTEXT_LINE=$((i + 4)) # Assuming match is on line 4 + CONTEXT_LINE_CONTENT=$(printf '%s' "$output" | head -n "${CONTEXT_LINE}" | tail --lines 1) + assert_line_match "Context line ${CONTEXT_LINE} below match" "$CONTEXT_LINE_CONTENT" "$output" + done + + # Check context lines above the match + for ((i = 1; i <= expected_context; i++)); do + CONTEXT_LINE=$((4 - i)) # Assuming match is on line 4 + CONTEXT_LINE_CONTENT=$(printf '%s' "$output" | head -n ${CONTEXT_LINE} | tail -n 1) + assert_line_match "Context line ${CONTEXT_LINE} above match" "$CONTEXT_LINE_CONTENT" "$output" + done + + cd "$current_path" || { + fail "(${LINENO}) It was not possible to move back from temp directory" + return + } +} + function test_parse_explore_options() { # Expected behaviour @@ -236,6 +290,20 @@ function test_parse_explore_options() assertEquals "($LINENO)" '0' "$ret" assertEquals "($LINENO)" 'HEADER' "${options_values['SCOPE']}" + unset options_values + declare -gA options_values + parse_explore_options -C + ret="$?" + assertEquals "(${LINENO})" '0' "$ret" + assertEquals "(${LINENO})" '3' "${options_values['CONTEXT']}" + + unset options_values + declare -gA options_values + parse_explore_options --show-context=5 + ret="$?" + assertEquals "(${LINENO})" '0' "$ret" + assertEquals "(${LINENO})" '5' "${options_values['CONTEXT']}" + # Others parse_explore_options --logljkl ret="$?" @@ -266,6 +334,13 @@ function test_parse_explore_options() assertEquals "($LINENO)" '22' "$ret" assertEquals "($LINENO)" 'Invalid arguments: Multiple search type!' "${options_values['ERROR']}" + unset options_values + declare -gA options_values + parse_explore_options --show-context=invalid + ret="$?" + assertEquals "($LINENO)" '22' "$ret" + assertEquals "($LINENO)" 'Context value must be a non-negative integer!' "${options_values['ERROR']}" + parse_explore_options main ret="$?" assertEquals "($LINENO)" '0' "$ret"