├── scripts
└── test
├── .editorconfig
├── test
├── helper.fish
└── cd.completion.fish
├── conf.d
└── cd.fish
├── LICENSE
├── README.md
├── completions
└── cd.fish
└── functions
└── __plugin_cd.fish
/scripts/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env fish
2 |
3 | set -l SOURCE_DIR (cd ( dirname (status --current-filename) ); and pwd)
4 |
5 | eval $SOURCE_DIR/../test/cd.completion.fish
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | indent_style = space
10 | indent_size = 2
11 |
12 |
--------------------------------------------------------------------------------
/test/helper.fish:
--------------------------------------------------------------------------------
1 | set -l fish_tank /usr/local/share/fish-tank/tank.fish
2 | if not test -e $fish_tank
3 | echo 'Install fish-tank for running the tests (https://github.com/terlar/fish-tank)'
4 | git clone --depth 1 https://github.com/terlar/fish-tank.git /tmp/fish-tank
5 | cd /tmp/fish-tank/
6 | make install
7 | end
8 |
9 | source $fish_tank
10 |
11 | set -U tank_reporter spec
12 |
13 |
14 |
--------------------------------------------------------------------------------
/conf.d/cd.fish:
--------------------------------------------------------------------------------
1 | set SOURCE_DIR (dirname (status -f))
2 |
3 | if not functions -q __wrapped_cd
4 | functions -c cd __wrapped_cd
5 | functions -e cd
6 | end
7 |
8 | functions -c __plugin_cd cd
9 |
10 | function _cd_uninstall --on-event cd_uninstall
11 | functions -e cd
12 |
13 | if functions -q __wrapped_cd
14 | functions -c __wrapped_cd cd
15 | functions -e __wrapped_cd
16 | end
17 |
18 | functions -e __plugin_cd
19 | end
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jianming Qu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![][travis-badge]][travis-link]
2 | ![][license-badge]
3 |
4 |
9 |
10 |
11 | # cd
12 |
13 | Plugin for [Oh My Fish][omf-link].
14 |
15 | Package cd provides a new `cd` command to help you change the current working directory fast. It's a wrapper directive of the built-in `cd` command with many useful features and powerful completions.
16 |
17 | ## Features:
18 |
19 | - Multi-dot navigation: `cd ....`
20 | - Plus / minus navigation: `cd -` `cd -3` `cd +2`
21 | - Full `$CDPATH` support
22 |
23 | ## Install
24 |
25 | ```fish
26 | $ omf install cd
27 | ```
28 |
29 |
30 | ## Usage
31 |
32 | ```fish
33 | $ cd .../foo # <=> cd ../../foo
34 | $ cd ... # <=> cd ../..
35 | $ cd .../foo/.../bar # <=> cd ../../foo/../../bar
36 | ```
37 |
38 | ```fish
39 | $ cd # ~
40 | $ cd ~/foo # ~/foo
41 | $ cd ~/bar # ~/bar
42 | $ cd - # ~/foo ;; ( Equal to `cd -1` )
43 | ```
44 |
45 | ```fish
46 | $ pwd # ~/a
47 | $ cd ~/b # ~/b ;; ( dirstack: a )
48 | $ cd ~/c # ~/c ;; ( dirstack: b a )
49 | $ cd ~/d # ~/d ;; ( dirstack: c b a )
50 | $ cd -2 # ~/b ;; ( dirstack: d c a )
51 | $ cd +1 # ~/c ;; ( dirstack: b d a )
52 | $ cd +0 # ~/a ;; ( dirstack: c b d )
53 | $ cd -0 # ~/a ;; ( dirstack: c b d )
54 | ```
55 |
56 | # License
57 |
58 | [MIT][mit] © [Jianming Qu](https://jmqu.tech)
59 |
60 |
61 | [mit]: http://opensource.org/licenses/MIT
62 | [author]: http://github.com/sancoder-q
63 | [omf-link]: https://www.github.com/oh-my-fish/oh-my-fish
64 |
65 | [license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square
66 | [travis-badge]: http://img.shields.io/travis/sancoder-q/plugin-cd.svg?style=flat-square
67 | [travis-link]: https://travis-ci.org/sancoder-q/plugin-cd
68 |
--------------------------------------------------------------------------------
/completions/cd.fish:
--------------------------------------------------------------------------------
1 | function __complete_omf_cd
2 | function __resolve_dot_path
3 | echo $argv | \
4 | sed -e 's@^\.$@:@;s@^\.\([^\.]\)@:\1@g;s@\([^\.]\)\.$@\1:@g' \
5 | -e 's@\([^\.]\)\.\([^\.]\)@\1:\2@g' \
6 | -e 's@\([^\.]\)\.\([^\.]\)@\1:\2@g' \
7 | -e 's@\.\{2\}\(\.*\)@::\1@g' \
8 | -e 's@\.@\/\.\.@g' \
9 | -e 's@:@\.@g'
10 | end
11 |
12 | function __resolve_home_path
13 | echo $argv |\
14 | sed -e "s@^~@$HOME@"
15 | end
16 |
17 | function __resolve_fancy_path
18 | __resolve_dot_path (__resolve_home_path $argv)
19 | end
20 |
21 | function __get_basepath
22 | set -l __base (echo $argv | rev | cut -d/ -s -f2- | rev)
23 | if string match -qr '^/' "$argv"; and test -z $__base
24 | echo '/'
25 | else
26 | echo $__base
27 | end
28 | end
29 |
30 | function __get_filename
31 | echo $argv | rev | cut -d/ -f1 | rev
32 | end
33 |
34 | function __filter_directory
35 | while read -l __name
36 | if test -d "$argv/$__name"
37 | echo "$__name/"
38 | end
39 | end
40 | end
41 |
42 | function __list_and_filter
43 | if test -d $argv[1]
44 | command ls -a $argv[1] | __filter_directory $argv[1] | \
45 | command grep -e (echo $argv[2] | sed -e 's@\.@\\\.@g' -e 's@^@\^@')
46 | end
47 | end
48 |
49 | function __list_all_candidate
50 |
51 | set -l __basepath (__get_basepath $argv)
52 | set -l __filename (__get_filename $argv)
53 |
54 | if test -n "$__basepath"
55 | set __candidate (__list_and_filter $__basepath $__filename)
56 | else
57 | set __candidate (__list_and_filter . $__filename)
58 | end
59 |
60 | if test -n "$CDPATH"
61 | for cdpath in $CDPATH
62 | if not string match -qr '^/' $argv
63 | set -l resolved_cdpath (__resolve_home_path $cdpath)
64 | if test -d $resolved_cdpath
65 | set __candidate $__candidate (__list_and_filter "$resolved_cdpath/$__basepath" $__filename)
66 | end
67 | end
68 | end
69 | end
70 |
71 | for __c in $__candidate
72 | if test "$__c" = './'; or test "$__c" = '../'
73 | else if test -z "$__c"
74 | else
75 | echo $__c
76 | end
77 | end
78 | end
79 |
80 | function __list_all
81 |
82 | set -l token $argv
83 |
84 | if test "$token" = "~"
85 | echo "~/"
86 | return 0
87 | end
88 |
89 | set -l basepath (__get_basepath $token)
90 | set -l resolved_path (__resolve_fancy_path $token)
91 |
92 | if test -d $resolved_path; and string match -qr '[^/]$' $resolved_path
93 | echo "$token/"
94 | end
95 |
96 | if test -z "$basepath"
97 | __list_all_candidate $resolved_path
98 | else if test "$basepath" = '/'
99 | __list_all_candidate $resolved_path | sed "s@^@/@"
100 | else
101 | __list_all_candidate $resolved_path | sed "s@^@$basepath/@"
102 | end
103 | end
104 |
105 | __list_all $argv | sort -u
106 | end
107 |
108 | function __fish_complete_plugin_cd -d "Completions for the plugin-cd command"
109 | # Start
110 | set -l token (commandline -ct)
111 | __complete_omf_cd $token
112 | end
113 |
114 | complete -c cd -e
115 | complete -f -c cd -a "(__fish_complete_plugin_cd)"
116 |
--------------------------------------------------------------------------------
/functions/__plugin_cd.fish:
--------------------------------------------------------------------------------
1 | # SYNOPSIS
2 | # cd [DIRECTORY|DOTS]
3 | #
4 | # Description
5 | # cd changes the current working directory. It's a proxy directive of the buildin
6 | # cd command with an alias of going to the upper directory.
7 | #
8 | # Usage
9 | # cd - # return to the last directory
10 | # cd -1 # equal to cd -
11 | # cd -2 # navigate to the second top directory from dirstack
12 | # cd +2 # navigate to the second bottom directory from dirstack
13 | #
14 | # Examples
15 | # cd .../foo <=> cd ../../foo
16 | # cd ... <=> cd ../..
17 | # cd .../foo/.../bar <=> cd ../../foo/../../bar
18 |
19 | function __plugin_cd -d "plugin-cd" -a fancy_path
20 |
21 | # private:
22 | function __fish_cd
23 | if functions -q __wrapped_cd
24 | __wrapped_cd $argv
25 | else
26 | builtin cd $argv
27 | end
28 | end
29 |
30 | function __empty_cd -S
31 | __fish_cd
32 | set -l output_status $status
33 |
34 | __update_pwd
35 |
36 | return $output_status
37 | end
38 |
39 | function __hyphen_cd -S
40 | __fish_cd $OLDPWD
41 | set -l output_status $status
42 |
43 | __update_pwd
44 |
45 | return $output_status
46 | end
47 |
48 | function __fancy_cd -S
49 | set extract_from_right (echo $fancy_path | sed -n 's/^+\([0-9]*\)$/\1/g p')
50 | set extract_from_left (echo $fancy_path | sed -n 's/^-\([0-9]*\)$/\1/g p')
51 |
52 | if test -n "$extract_from_right" -o -n "$extract_from_left"
53 | # Generate current stack
54 | set -l stack (command pwd) $dirstack
55 |
56 | if test -n "$extract_from_right"
57 | if test $extract_from_right -gt (math (count $stack) - 1)
58 | echo "no such entry in dir stack"
59 | return 1
60 | end
61 |
62 | set extract_from_left (math (count $stack) - 1 - $extract_from_right)
63 | end
64 |
65 | if test $extract_from_left -gt (math (count $stack) - 1)
66 | echo "no such entry in dir stack"
67 | return 1
68 | else
69 | if test $extract_from_left -gt 0
70 | set stack $stack[(math $extract_from_left + 1)] $stack
71 | set -e stack[(math $extract_from_left + 2)]
72 | end
73 |
74 | # now reconstruct dirstack and change directory
75 | set -g dirstack $stack[2..(count $stack)]
76 | __fish_cd $stack[1]
77 | set -l output_status $status
78 |
79 | __update_pwd
80 |
81 | return $output_status
82 | end
83 | else
84 | set -l normal_path (echo $fancy_path | sed -e 's@^\.$@:@;s@^\.\([^\.]\)@:\1@g;s@\([^\.]\)\.$@\1:@g' -e 's@\([^\.]\)\.\([^\.]\)@\1:\2@g' -e 's@\([^\.]\)\.\([^\.]\)@\1:\2@g' -e 's@\.\{2\}\(\.*\)@::\1@g' -e 's@\.@\/\.\.@g' -e 's@:@\.@g')
85 |
86 | __fish_cd $normal_path
87 | set -l output_status $status
88 |
89 | __update_pwd
90 |
91 | return $output_status
92 | end
93 | end
94 |
95 | function __update_dirstack -S
96 | if contains $old_pwd $dirstack
97 | # If the from directory is existed in stack, remove it
98 | set -e dirstack[(contains -i $old_pwd $dirstack)]
99 | end
100 | # Add the from directory to the head of stack
101 | set -g dirstack $old_pwd $dirstack
102 |
103 | if contains (command pwd) $dirstack
104 | # If the to directory is existed in stack, remove it
105 | set -e dirstack[(contains -i (command pwd) $dirstack)]
106 | end
107 | end
108 |
109 | function __update_pwd -S
110 | if test $old_pwd != (command pwd)
111 | set -xg OLDPWD $old_pwd
112 | __update_dirstack
113 | end
114 | end
115 |
116 | # body:
117 | set -l old_pwd (pwd)
118 |
119 | switch "$fancy_path"
120 | case ''
121 | __empty_cd
122 | case '-'
123 | __hyphen_cd
124 | case '*'
125 | __fancy_cd
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/test/cd.completion.fish:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/fish
2 |
3 | source (dirname (status -f))/../completions/cd.fish
4 | set _test_path /tmp/omf-cd
5 |
6 | function before
7 | mkdir -p "$_test_path"
8 | mkdir -p "$_test_path/.a/tm_start_folder"
9 | mkdir -p "$_test_path/a"
10 | mkdir -p "$_test_path/b"
11 | mkdir -p "$_test_path/b/b"
12 | mkdir -p "$_test_path/b/b1"
13 | mkdir -p "$_test_path/b/b2"
14 | mkdir -p "$_test_path/c"
15 | mkdir -p "$_test_path/c/~/h1"
16 | mkdir -p "$_test_path/c/~/h1/h1.1"
17 | mkdir -p "$_test_path/c/~/h2"
18 |
19 | set -e CDPATH
20 | set HOME $_test_path/c/~
21 | end
22 |
23 | function after
24 | set -e HOME
25 | rm -rf $_test_path
26 | end
27 |
28 | function suite_relative_path
29 |
30 | function test_empty_should_return_current_directories
31 | cd $_test_path
32 | set -l subject (__complete_omf_cd "")
33 | assert_equal ".a/ a/ b/ c/" $subject
34 | end
35 |
36 | function test_one_dot_only_should_return_directories_stating_with_a_dot
37 | cd $_test_path
38 | assert_equal "./ .a/" (__complete_omf_cd ".")
39 | end
40 |
41 | function test_two_dot_only_should_return_a_slash
42 | cd $_test_path
43 | assert_equal "../" (__complete_omf_cd "..")
44 | end
45 |
46 | function test_three_dot_only_should_return_a_slash
47 | cd $_test_path
48 | assert_equal ".../" (__complete_omf_cd "...")
49 | end
50 |
51 | function test_continous_slashs_should_not_change_the_results
52 | cd $_test_path
53 | assert_equal ".//./ .//.a/" (__complete_omf_cd ".//.")
54 | end
55 |
56 | function test_one_dot_ending_with_slash_should_return_current_directories
57 | cd $_test_path
58 | assert_equal "./.a/ ./a/ ./b/ ./c/" (__complete_omf_cd "./")
59 | end
60 |
61 | function test_two_dot_with_slash_should_return_parent_directories
62 | cd $_test_path/a
63 | assert_equal "../.a/ ../a/ ../b/ ../c/" (__complete_omf_cd "../")
64 | end
65 |
66 | function test_empty_path_should_return_empty
67 | cd $_test_path
68 | assert_empty (__complete_omf_cd "a/")
69 | end
70 | end
71 |
72 | function suite_absolute_path
73 |
74 | function test_root_slash_should_return_root_directories
75 | cd $_test_path
76 | set -l subject (__complete_omf_cd "/")
77 | assert (contains '/tmp/' $subject)
78 | assert (contains '/etc/' $subject)
79 | end
80 |
81 | function test_empty_should_return_current_directories
82 | cd $_test_path
83 | set -l subject (__complete_omf_cd "$_test_path/")
84 | assert_equal "$_test_path/.a/ $_test_path/a/ $_test_path/b/ $_test_path/c/" $subject
85 | end
86 |
87 | function test_one_dot_only_should_return_directories_stating_with_a_dot
88 | cd $_test_path
89 | assert_equal "$_test_path/./ $_test_path/.a/" (__complete_omf_cd "$_test_path/.")
90 | end
91 |
92 | function test_two_dot_only_should_return_a_slash
93 | cd $_test_path
94 | assert_equal "$_test_path/../" (__complete_omf_cd "$_test_path/..")
95 | end
96 |
97 | function test_three_dot_only_should_return_a_slash
98 | cd $_test_path
99 | assert_equal "$_test_path/.../" (__complete_omf_cd "$_test_path/...")
100 | end
101 |
102 | function test_one_dot_ending_with_slash_should_return_current_directories
103 | cd $_test_path
104 | assert_equal "$_test_path/./.a/ $_test_path/./a/ $_test_path/./b/ $_test_path/./c/" \
105 | (__complete_omf_cd "$_test_path/./")
106 | end
107 |
108 | function test_two_dot_with_slash_should_return_parent_directories
109 | cd $_test_path
110 | assert_equal "$_test_path/a/../.a/ $_test_path/a/../a/ $_test_path/a/../b/ $_test_path/a/../c/" \
111 | (__complete_omf_cd "$_test_path/a/../")
112 | end
113 |
114 | function test_empty_path_should_return_empty
115 | cd $_test_path
116 | assert_empty (__complete_omf_cd "$_test_path/a/")
117 | end
118 |
119 | function test_one_swung_should_return_a_slash
120 | cd $_test_path
121 | assert_equal "~/" (__complete_omf_cd "~")
122 | end
123 |
124 | function test_one_swung_with_slash_should_return_home_directories
125 | cd $_test_path
126 | assert_equal "~/h1/ ~/h2/" (__complete_omf_cd "~/")
127 | end
128 |
129 | function test_path_start_with_swung_should_return_correct_directories
130 | cd $_test_path
131 | assert_equal "~/h1/h1.1/" (__complete_omf_cd "~/h1/")
132 | end
133 | end
134 |
135 | function suite_with_cdpath
136 | function test_single_cdpath_different_with_current_should_return_merged_directories
137 | cd $_test_path
138 | set -g CDPATH "$_test_path/b"
139 | set -l subject (__complete_omf_cd "")
140 | set -e CDPATH
141 | assert_equal ".a/ a/ b/ b1/ b2/ c/" $subject
142 | end
143 |
144 | function test_single_cdpath_with_slash_should_not_make_differences
145 | cd $_test_path
146 | set -g CDPATH "$_test_path/b/"
147 | set -l subject (__complete_omf_cd "")
148 | set -e CDPATH
149 | assert_equal ".a/ a/ b/ b1/ b2/ c/" $subject
150 | end
151 |
152 | function test_single_cdpath_same_with_current_should_return_current_directories
153 | cd $_test_path
154 | set -g CDPATH "$_test_path"
155 | set -l subject (__complete_omf_cd "")
156 | set -e CDPATH
157 | assert_equal ".a/ a/ b/ c/" $subject
158 | end
159 |
160 | function test_single_cdpath_with_root_should_not_return_directories_in_cdpath
161 | cd $_test_path
162 | set -g CDPATH "$_test_path/.a"
163 | set -l subject (__complete_omf_cd "/tm")
164 | set -e CDPATH
165 | assert (not contains '/tm_start_folder/' $subject)
166 | end
167 |
168 | function test_multiple_cdpath_should_return_correct_directories
169 | cd $_test_path
170 | set -g CDPATH "$_test_path/b" "$_test_path/c/~"
171 | set -l subject (__complete_omf_cd "")
172 | set -e CDPATH
173 | assert_equal ".a/ a/ b/ b1/ b2/ c/ h1/ h2/" $subject
174 | end
175 |
176 | function test_multiple_cdpath_include_swung_should_return_correct_directories
177 | cd $_test_path
178 | set -g CDPATH "$_test_path/b" "~"
179 | set -l subject (__complete_omf_cd "")
180 | set -e CDPATH
181 | assert_equal ".a/ a/ b/ b1/ b2/ c/ h1/ h2/" $subject
182 | end
183 |
184 | function test_multiple_cdpath_with_path_should_return_correct_directories
185 | cd $_test_path
186 | set -g CDPATH "$_test_path/b" "$_test_path/c/~"
187 | set -l subject (__complete_omf_cd "b")
188 | set -e CDPATH
189 | assert_equal "b/ b1/ b2/" $subject
190 | end
191 | end
192 |
193 | if not set -q tank_running
194 | source (dirname (status -f))/helper.fish
195 | before
196 | tank_run
197 | after
198 | end
199 |
--------------------------------------------------------------------------------