├── README.md
├── LICENSE
└── shellect
/README.md:
--------------------------------------------------------------------------------
1 | # shellect
2 |
3 | shellect is a selection system written in POSIX shell.
4 |
5 | ## Table of content
6 |
7 |
8 |
9 |
10 | * [Preview](#preview)
11 | * [Dependency](#dependency)
12 | * [Introduction](#introduction)
13 | * [Projects that is using `shellect`](#projects-that-is-using-shellect)
14 | * [Implementation Details](#implementation-details)
15 | * [Explanation for TUI manipulation](#explanation-for-tui-manipulation)
16 | * [Overcome the limitation of POSIX shell](#overcome-the-limitation-of-posix-shell)
17 |
18 |
19 |
20 | ## Preview
21 |
22 | [](https://asciinema.org/a/jLJay0bFv0mqSfcnWbAWYiVwu)
23 |
24 | ## Dependency
25 |
26 | 1. POSIX-compliant shell: printf, set, unset, shift, test, while, continue, break, return, case, trap, getopts
27 | 2. stty
28 | 3. dd
29 | 4. cat
30 |
31 | ## Introduction
32 |
33 | shellect will either accept standard input or assign the display content by `-c` option, i.e., to display all the non-hidden files and directories in your `$HOME` directory,
34 |
35 | ```sh
36 | printf '%s\n' $HOME/* | shellect # standard input
37 | shellect -c "$HOME/*" # -c option
38 |
39 | ```
40 |
41 | The keybindings are:
42 |
43 | ```
44 | k/↑/Ctrl-p - up
45 | j/↓/Ctrl-n - down
46 | l/→ - right
47 | h/← - left
48 | Ctrl-f/PageDown - PageDown
49 | Ctrl-u/PageUp - PageUp
50 | g/Home/Ctrl-a - go to top
51 | G/End/Ctrl-e - go to bottom
52 | / - search
53 | ? - show keybinds
54 | q - quit
55 | ```
56 |
57 | Command-line option:
58 |
59 | ```
60 | Usage:
61 |
62 | shellect [OPTIONS] ([ARGS])
63 |
64 | -h, Show help options
65 | -i, Set case-insensitive search
66 | -l, Set live-search
67 | -n=[num], Set numbers of line per entry
68 | -d=[delim], Set delimiter (IFS, internal field separator)
69 | -c=[content], Set content to display
70 | -f=[format], Set the format to print out content
71 | -t=[msg], Set top status bar message
72 | -b=[msg], Set bottom status bar message
73 |
74 | format detail:
75 | nldel delete last nl, equiv to "\${1%\$nl}"
76 | basename only print basename, equiv to "\${1##*/}" ;;
77 |
78 | if unset or empty, then equiv to "\$1"
79 |
80 | live-search detail:
81 | Enter confirm
82 | Backspace delete previous character
83 | Tab Tab-completion forward
84 | Shift-Tab Tab-completion backward
85 | control char ignore
86 | others print out
87 | ```
88 |
89 | ## Projects that is using `shellect`
90 |
91 | - [bib.awk](https://github.com/huijunchen9260/bib.awk)
92 | - [dmenufm](https://github.com/huijunchen9260/dmenufm)
93 |
94 | ## Implementation Details
95 |
96 | shellect born from my experience in developing my bibliography manager, [`shbib`](https://github.com/huijunchen9260/shbib), and I built [`shbib`](https://github.com/huijunchen9260/shbib) on the basis provided by [`shfm`](https://github.com/dylanaraps/shfm). I realized that if I do not obey the Unix philosophy and keep adding functions to [`shbib`](https://github.com/huijunchen9260/shbib), [`shbib`](https://github.com/huijunchen9260/shbib) would grow exponentially and eventually become a pain to maintain. Therefore, I isolate out shellect as an individual selection system that just written in POSIX shell.
97 |
98 | ### Explanation for TUI manipulation
99 |
100 | Basically, printing out the raw escape sequence to manipulate the terminal output works in most terminal, and its function is way richer than `tput`.
101 | However, printing out these escape sequence can be daunting, and it is often time-consuming to remember the function of each arbitrary sequence.
102 | Thus, The following `esc` function is steal from [`shfm`](https://github.com/dylanaraps/shfm), with some of my own comment and modification to facilitate the understanding.
103 | All of the resource can be found in the following three resources:
104 |
105 | - Reference for color: https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
106 | - Reference for vt100: https://vt100.net/docs/vt510-rm/contents.html
107 | - Reference for escape sequence: https://github.com/dylanaraps/pure-sh-bible#escape-sequences
108 |
109 | ```sh
110 | esc() {
111 | case $1 in
112 | # vt100 (IL is vt102) (DECTCEM is vt520)
113 | CUP) printf '%s[%s;%sH' "$esc_c" "$2" "$3" ;;
114 | # cursor to LINES($2), COLUMNS($3)
115 | CUU) printf '%s[%sA' "$esc_c" "$2" ;;
116 | # cursor up
117 | CUD) printf '%s[%sB' "$esc_c" "$2" ;;
118 | # cursor down
119 | CUR) printf '%s[%sC' "$esc_c" "$2" ;;
120 | # cursor right
121 | CUL) printf '%s[%sD' "$esc_c" "$2" ;;
122 | # cursor left
123 | DECAWM) printf '%s[?7%s' "$esc_c" "$2" ;;
124 | # (h: set; l: unset) line wrap
125 | DECRC) printf '%s8' "$esc_c" ;;
126 | # cursor restore
127 | DECSC) printf '%s7' "$esc_c" ;;
128 | # cursor save
129 | DECSTBM) printf '%s[%s;%sr' "$esc_c" "$2" "$3" ;;
130 | # scroll region ($2: top; $3: bottom)
131 | DECSLRM) printf '%s[%s;%ss' "$esc_c" "$2" "$3" ;;
132 | # Set left and right margin
133 | DECTCEM) printf '%s[?25%s' "$esc_c" "$2" ;;
134 | # (h: show; l: hide) cursor visible
135 | ED[0-2]) printf '%s[%sJ' "$esc_c" "${1#ED}" ;;
136 | # Erase Display:
137 | # 0: From the cursor through the end of the display
138 | # 1: From the beginning of the display through the cursor
139 | # 2: The complete display
140 | EL[0-2]) printf '%s[%sK' "$esc_c" "${1#EL}" ;;
141 | # Erase Line:
142 | # 0: from cursor to end of the line
143 | # 1: from beginning of the line to cursor
144 | # 2: entire line
145 | IL) printf '%s[%sL' "$esc_c" "$2" ;;
146 | # insert blank line
147 | SGR) printf '%s[%s;%sm' "$esc_c" "$2" "$3" ;;
148 | # colors ($2); attribute ($3)
149 |
150 | # Color list:
151 | # FG BG
152 | # Black 30 40
153 | # Red 31 41
154 | # Green 32 42
155 | # Yellow 33 43
156 | # Blue 34 44
157 | # Magenta 35 45
158 | # Cyan 36 46
159 | # White 37 47
160 | # Bright Black 90 100
161 | # Bright Red 91 101
162 | # Bright Green 92 102
163 | # Bright Yellow 93 103
164 | # Bright Blue 94 104
165 | # Bright Magenta 95 105
166 | # Bright Cyan 96 106
167 | # Bright White 97 107
168 |
169 | # Attribute list:
170 | # Reset 0/''
171 | # Bold 1
172 | # Faint 2
173 | # Italic 3
174 | # Underline 4
175 | # Slow blink 5
176 | # Swap foreground and background colors. 7
177 | # Hidden 8
178 | # Strike-through 9
179 |
180 | # xterm (since 1988, supported widely)
181 | screen_alt) printf '%s[?1049%s' "$esc_c" "$2" ;; # (h: to; l: back from) alternate buffer
182 | esac
183 | }
184 | ```
185 |
186 | ### Overcome the limitation of POSIX shell
187 |
188 | POSIX shell is very limited, and quite inefficient compared to compiling language.
189 | Previously, I experimented the efficiency of POSIX shell in terms of passing through all the argument array elements into the key detecting part:
190 |
191 | > The efficiency of shellect is highly constraint by the total number of entries and the content that you want to display.
192 | > With `bash`, as I tested, probably only numbers of 5000 is large enough to create significant lag. The command I run is `tree /directory/have/5000/subitems | shellect` or `echo $(seq 1 5000) | shellect`.
193 | > With `dash`, the efficiency is highly depends on both directions. At the number 20000, shellect runs fair efficiency. The command is `tree /directory/have/20000/subitems | shellect`. With the number of 30000, the pointer will not stop if I relieve my key press. However, changing the command to `echo $(seq 1 30000) | shellect`, in my computer, shellect runs with fair efficiency.
194 | > Comparing with `dmenu` and `fzf`, shellect is probably extremely inefficient in terms of large numbers of entry. This is probably the limitation of an interpreting language compared to compiling language.
195 |
196 | As an interpreting language, I found a way to avoid such inefficiency.
197 |
198 | First, I'll define some terminologies that I'll use through the explanation:
199 |
200 | 1. argument array: POSIX shell has no array type. However, there's actually one, and only one array in POSIX shell, i.e., the positional parameters, `$1`, `$2`, etc.
201 | To see more information, go to "Working with arrays" section in [Rich’s sh (POSIX shell) tricks](http://www.etalabs.net/sh_tricks.html).
202 | 2. selection: the item in argument array that is defined in `$cur`.
203 | 3. Length of array: access by `$#`. The length of the total content is `$last`, and the length of a list is `$len`. `$len` is set to 500 if the length of the total content, `$last`, is larger than 500.
204 |
205 |
206 | ```sh
207 | while key=$(dd ibs=1 count=1 2>/dev/null); do
208 | ...
209 | done
210 | ```
211 |
212 | This `while` loop is the part to detect key press.
213 | This `dd` command has nothing to interact with the argument array.
214 | However, `dd`'s efficiency will be highly affected by argument array that just pass through it.
215 | If the total number of argument array is too high, then `dd` will become laggy when reading the key press, eventually causing the cursor movement is laggy.
216 |
217 | To resolve this limitation. I developed a technique to only feed part of the total content to the above while loop:
218 |
219 | ```sh
220 | key() {
221 | input_assign ... # Generate a list which is part of the total content
222 |
223 | set -- $list # let the list to be argument array
224 |
225 | while key=$(dd ibs=1 count=1 2>/dev/null); do
226 | ...
227 | othercommand
228 | return 0 # Go back to main function and stay in the while loop in main function
229 | ...
230 | selection key pressed
231 | return 1 # Go back to main and leave the while loop in main function
232 | done
233 | }
234 |
235 | main() {
236 | ...
237 | while [ $? -eq 0 ]; do # If return 0, stay in while loop; others, leave the while loop
238 | set -- $content # total content
239 | key "$@"
240 | done
241 | }
242 | ```
243 |
244 | The following steps are to actively switch between `main` function and `key` function.
245 | Within the `$list`, the selection stay in the `while key` loop. If the current selection ever go out of the `$list`, then go back to `main` function, reload the `$content` and generate new `$list`, back to `while key` loop, and process again.
246 | That is to say, user will experience an one-time inaction when reach the boundary of `$list`.
247 | This inaction is to renew `$list` to match current position in the whole `$content`.
248 | Press again, and the selection will move to the next item.
249 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 |
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, see .
308 |
309 | Also add information on how to contact you by electronic and paper mail.
310 |
311 | If the program is interactive, make it output a short notice like this
312 | when it starts in an interactive mode:
313 |
314 | Gnomovision version 69, Copyright (C) year name of author
315 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
316 | This is free software, and you are welcome to redistribute it
317 | under certain conditions; type `show c' for details.
318 |
319 | The hypothetical commands `show w' and `show c' should show the appropriate
320 | parts of the General Public License. Of course, the commands you use may
321 | be called something other than `show w' and `show c'; they could even be
322 | mouse-clicks or menu items--whatever suits your program.
323 |
324 | You should also get your employer (if you work as a programmer) or your
325 | school, if any, to sign a "copyright disclaimer" for the program, if
326 | necessary. Here is a sample; alter the names:
327 |
328 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
329 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
330 |
331 | , 1 April 1989
332 | Moe Ghoul, President of Vice
333 |
334 | This General Public License does not permit incorporating your program into
335 | proprietary programs. If your program is a subroutine library, you may
336 | consider it more useful to permit linking proprietary applications with the
337 | library. If this is what you want to do, use the GNU Lesser General
338 | Public License instead of this License.
339 |
--------------------------------------------------------------------------------
/shellect:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # shellect - an interactive select utility for POSIX shells
4 | # Copyright (C) 2025 Hui-Jun Chen
5 | #
6 | # This program is free software; you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation; either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License along
17 | # with this program; if not, see .
18 |
19 | # Variable explain:
20 | # $y: the current argument in the whole content
21 | # $yy: the current argument in the list
22 | # $pos: the current position on argument y
23 | # $y2: the current position on current screen position
24 | # $num: numbers of lines per argument
25 | # $fin: bottom for printable area, depends on num
26 | # $token: indicator for actions
27 | # Dimension is adjustable by $top & $bottom
28 |
29 | # Copied from shfm under MIT License:
30 | # Copyright (c) 2020 Dylan Araps
31 | # https://github.com/dylanaraps/shfm
32 | esc() {
33 | case $1 in
34 | # vt100 (IL is vt102) (DECTCEM is vt520)
35 | CUP) printf '%s[%s;%sH' "$esc_c" "$2" "$3" ;;
36 | # cursor to LINES($2), COLUMNS($3)
37 | CUU) printf '%s[%sA' "$esc_c" "$2" ;;
38 | # cursor up
39 | CUD) printf '%s[%sB' "$esc_c" "$2" ;;
40 | # cursor down
41 | CUR) printf '%s[%sC' "$esc_c" "$2" ;;
42 | # cursor right
43 | CUL) printf '%s[%sD' "$esc_c" "$2" ;;
44 | # cursor left
45 | DECAWM) printf '%s[?7%s' "$esc_c" "$2" ;;
46 | # (h: set; l: unset) line wrap
47 | DECRC) printf '%s8' "$esc_c" ;;
48 | # cursor restore
49 | DECSC) printf '%s7' "$esc_c" ;;
50 | # cursor save
51 | DECSTBM) printf '%s[%s;%sr' "$esc_c" "$2" "$3" ;;
52 | # scroll region
53 | DECTCEM) printf '%s[?25%s' "$esc_c" "$2" ;;
54 | # (h: show; l: hide) cursor visible
55 | ED[0-2]) printf '%s[%sJ' "$esc_c" "${1#ED}" ;;
56 | # clear screen
57 | EL[0-2]) printf '%s[%sK' "$esc_c" "${1#EL}" ;;
58 | # clear line
59 | IL) printf '%s[%sL' "$esc_c" "$2" ;;
60 | # insert line
61 | SGR) printf '%s[%s;%sm' "$esc_c" "$2" "$3" ;;
62 | # colors ($2); attribute ($3)
63 |
64 | # xterm (since 1988, supported widely)
65 | screen_alt) printf '%s[?1049%s' "$esc_c" "$2" ;;
66 | # (h: to; l: back from) alternate buffer
67 | esac
68 | }
69 |
70 | term_setup() {
71 | bottom=$((LINES - 2)) # space for bottom status_line
72 | top=3 # space for top status_line
73 | fin=$(( bottom - (bottom - (top - 1)) % num )) # bottom for content printed
74 |
75 | stty=$(stty -g)
76 | stty -icanon -echo
77 | esc screen_alt h
78 | esc DECAWM l
79 | esc DECTCEM l
80 | esc ED2
81 | esc DECSTBM "$top" "$fin"
82 | }
83 |
84 | term_getsize() {
85 | # false-positive, behavior intentional, globbing is disabled.
86 | # shellcheck disable=2046
87 | {
88 | set -f -- $(stty size)
89 | set +f
90 | }
91 |
92 | LINES=$1 COLUMNS=$2
93 | }
94 |
95 | term_reset() {
96 | esc DECAWM h >&2
97 | esc DECTCEM h >&2
98 | esc ED2 >&2
99 | esc DECSTBM >&2
100 | esc screen_alt l >&2
101 | stty "$stty"
102 | }
103 |
104 | term_init() {
105 | term_setup
106 | stty_init="$stty"
107 | }
108 |
109 | term_exit() {
110 | stty="$stty_init"
111 | term_reset
112 | }
113 |
114 | old_save() {
115 | old_num=$num
116 | old_y=$y
117 | old_yy=$yy
118 | old_y2=$y2
119 | old_pos=$pos
120 | old_format=$format
121 | old_msg=$msg
122 | old_ltype=$ltype
123 | old_last=$last
124 | old_len=$len
125 | }
126 |
127 | old_reset() {
128 | num=$old_num
129 | y=$old_y
130 | yy=$old_yy
131 | y2=$old_y2
132 | pos=$old_pos
133 | format=$old_format
134 | msg=$old_msg
135 | ltype=$old_ltype
136 | last=$old_last
137 | len=$old_len
138 | }
139 |
140 | input_assign() {
141 | offset=$1
142 | length=$2
143 |
144 | shift "$(( offset > 0 ? offset : 0 ))"; shift 2;
145 |
146 | i=1
147 | while [ "$i" -le "$length" ]; do
148 | save_list="$save_list${ifs:=$nl}$1"
149 | shift $(( $# > 0 ? 1 : 0 ))
150 | i=$(( i + 1 ))
151 | done
152 |
153 | save_list="${save_list#${ifs:=$nl}}"
154 | list=$save_list
155 |
156 | unset i offset length save_list
157 | }
158 |
159 |
160 | redraw() {
161 |
162 | pos=$(( num * y - num + top ))
163 | end=$(( fin + 1 ))
164 | width=$(( (end - top) / num ))
165 |
166 | input_assign "$(( y >= width ? y - width : 0 ))" "$width" "$@"
167 |
168 | IFS=${ifs:=$nl}
169 | # shellcheck disable=2086
170 | set -- $list
171 | unset IFS
172 |
173 | esc ED2
174 |
175 | status_line "$last"
176 | list_print "$@"
177 |
178 | unset list
179 | }
180 |
181 |
182 | list_print() {
183 | esc CUP "$top"
184 |
185 | unset cur
186 |
187 | i=1
188 |
189 | for file do
190 |
191 | case $(( num * i - num + top )) in
192 | "$y2") cur=$file; save_cur=$file ;;
193 | *) cur= ;;
194 | esac
195 |
196 | case $(( num * i - num + top - end )) in
197 | -*)
198 | arg_format "$file"
199 | esc CUD
200 | i=$((i + 1))
201 | ;;
202 | 0|*) break ;;
203 | esac
204 |
205 | done
206 |
207 | esc CUP "$(( pos > y2 ? y2 : pos ))"
208 |
209 | cur=$save_cur
210 | unset save_cur
211 | }
212 |
213 | arg_print() {
214 | shift "$1"
215 | arg_format "$1"
216 | }
217 |
218 |
219 | arg_format() {
220 | esc EL0
221 |
222 | # overall display rule
223 | case "$cur" in
224 | '') esc CUR 5; esc SGR '' 2 ;;
225 | *) esc CUR 2; esc SGR 35 1; printf '>'; esc SGR 33 1; esc CUR 2 ;;
226 | esac
227 |
228 | # content display format
229 | case "$format" in
230 | basename) printf '%s\r' "${1##*/}" ;;
231 | nldel) printf '%s\r' "${1%$nl}" ;;
232 | '') printf '%s\r' "$1" ;;
233 | esac
234 |
235 | esc SGR
236 | }
237 |
238 |
239 | term_move_up() {
240 |
241 | y=$(( y - move )) yy=$(( yy - move ))
242 | pos=$(( num * y - num + top ))
243 |
244 | # Generate list for partial input
245 | input_assign "$(( yy - 1 ))" "$(( move + 1 ))" "$@"
246 | IFS=${ifs:=$nl}
247 | # shellcheck disable=2086
248 | set -- $list
249 | unset IFS list cur
250 |
251 | i=1
252 | while [ "$i" -le "$move" ] ; do
253 | arg_print "$(( move - i + 2 ))" "$@" # print the latter item first
254 |
255 | case "$y2" in # adjust for upper bound
256 | "$top") [ "$num" -gt 1 ] && esc CUU $(( num - 1 )); esc IL "$num" ;;
257 | *) esc CUU "$(( num * 2 - 1 ))"; y2=$(( y2 > top ? y2 - num : top )) ;;
258 | esac
259 |
260 | i=$(( i + 1 ))
261 | done
262 |
263 | offset=$y
264 | cur=$1
265 | arg_format "$1"
266 |
267 | # Restore cursor to match y2
268 | [ "$num" -gt 1 ] && esc CUU "$(( num - 1 ))"
269 |
270 | status_line "$last"
271 |
272 | unset i offset
273 | }
274 |
275 | term_move_down() {
276 |
277 | y=$(( y + move )) yy=$(( yy + move ))
278 | pos=$(( num * y - num + top ))
279 | y2=$(( y2 + num * move < fin - (num - 1) ? y2 + num * move : fin - (num - 1) ))
280 |
281 | # Generate list for partial input
282 | input_assign "$(( yy - move - 1 ))" "$(( move + 1 ))" "$@"
283 | IFS=${ifs:=$nl}
284 | # shellcheck disable=2086
285 | set -- $list
286 | unset IFS cur
287 |
288 | i=1
289 | while [ "$i" -le "$move" ] ; do
290 | arg_format "$1"
291 | shift 1
292 | printf '\n'
293 |
294 | i=$(( i + 1 ))
295 | done
296 |
297 | offset=$y
298 | cur=$1
299 | arg_format "$1"
300 |
301 | [ "$num" -gt 1 ] && esc CUU "$(( num - 1 ))"
302 |
303 | status_line "$last"
304 |
305 | unset i offset
306 | }
307 |
308 | status_line () {
309 | esc DECSC
310 | esc CUP 1; esc EL2; printf '%s ' "($y/$1)"; printf "$msg"
311 | [ -n "$ltype" ] && { esc CUP "$LINES"; esc EL2; printf '%s' "$ltype"; }
312 | esc DECRC
313 | }
314 |
315 | prompt() {
316 | esc DECSC
317 | esc CUP "$LINES"
318 | printf %s "$1"
319 | esc DECTCEM h
320 | esc EL0
321 |
322 | case $2 in
323 | r)
324 | stty -cread icanon echo 1>/dev/null 2>&1
325 | read -r ans ||:
326 | stty -icanon -echo
327 | ;;
328 | l) press=$(dd ibs=1 count=1 2>/dev/null) ;;
329 | esac
330 |
331 | esc DECRC
332 | esc DECTCEM l
333 | }
334 |
335 | tab_complete() {
336 | shift "$(( count - 1 > 0 ? count - 1 : 0 ))"
337 | ans=$1
338 | }
339 |
340 | search() {
341 | search_list="$1"
342 | IFS="$2"
343 | str="$3"
344 |
345 | case $case_insense in
346 | 1) # case-insensitive filter
347 |
348 | # lowercase both search_list and str
349 | lower_search_list=$(printf '%s' "$search_list" | dd conv=lcase 2>/dev/null)
350 | lower_str=$(printf '%s' "$str" | dd conv=lcase 2>/dev/null)
351 |
352 | # First run: match lowercase and record the number of positional parameter
353 | i=1 s=1
354 | # shellcheck disable=2086
355 | set -- $lower_search_list
356 | for line do
357 | case $line in
358 | *$lower_str*)
359 | case $token in
360 | l) # live search: only search until bound
361 | case $((s - bound)) in
362 | -*|0) posnum="$posnum $i" ;;
363 | *) break ;;
364 | esac
365 | s=$(( s + 1 ))
366 | ;;
367 | /) posnum="$posnum $i" ;;
368 | esac
369 | esac
370 | i=$(( i + 1 ))
371 | done
372 | posnum=${posnum#* } # delete first space
373 |
374 | # Second run: match the item based on posnum above
375 | # shellcheck disable=2086
376 | set -- $search_list
377 |
378 | j=1
379 | for line do
380 | [ -z "$posnum" ] && break
381 | n=${posnum%% *} # first line number
382 | case $j in
383 | "$n")
384 | filter="$filter$IFS$line"
385 |
386 | case $posnum in # delete first number
387 | *[[:space:]]*) posnum=${posnum#* } ;;
388 | *) posnum= ;;
389 | esac
390 | ;;
391 | esac
392 | j=$(( j + 1 ))
393 | done
394 |
395 | ;;
396 | *) # case-sensitive filter
397 |
398 | s=1
399 |
400 | # shellcheck disable=2086
401 | set -- $search_list
402 | for line do
403 | case $line in
404 | *$str*)
405 | case $token in
406 | l) # live search: only search until bound
407 | case $((s - bound)) in
408 | -*|0) filter="$filter$IFS$line" ;;
409 | *) break ;;
410 | esac
411 | s=$(( s + 1 ))
412 | ;;
413 | /) filter="$filter$IFS$line" ;;
414 | esac
415 | esac
416 | done
417 |
418 | ;;
419 | esac
420 |
421 | filt_out=${filter#*$IFS}
422 |
423 | unset search_list IFS str filter posnum lower_list lower_str i j n s
424 |
425 | }
426 |
427 | key() {
428 |
429 | loop=$(( loop + 1 ))
430 |
431 | # Generate new list
432 | case $(( y - move )) in
433 | 1) y=1; yy=1; input_assign "0" "$len" "$@" ;;
434 | $(( last - move - 1 ))) y=$last; input_assign "$(( y - len ))" "$len" "$@" ;;
435 | *)
436 | case $(( y - len )) in
437 | -*) input_assign "$(( yy > 1 ? y - yy : y - 1 ))" "$len" "$@" ;;
438 | *) input_assign "$(( yy > 1 ? y - len : y - 1 ))" "$len" "$@" ;;
439 | esac
440 | esac
441 |
442 | IFS=${ifs:=$nl}
443 | # shellcheck disable=2086
444 | set -- $list
445 | unset IFS
446 |
447 | while key=$(dd ibs=1 count=1 2>/dev/null); do
448 |
449 | case $key${esc:=0} in
450 | "$ctrl_u"?|~5) # Ctrl-u / PageUp
451 | move=$(( width / 4 > 2 ? width / 4 : 2 ))
452 | # Terminal condition
453 | case $y in
454 | -*|0|1) continue ;;
455 | esac
456 | # Adjust move step
457 | case $(( y - move - 1 )) in
458 | -*|0) move=$(( y - 1 )) ;;
459 | esac
460 | # Move or update list
461 | case $(( yy - move )) in
462 | -*|0) yy=$(( y > len ? len : y )); return 0 ;;
463 | *) term_move_up "$@" ;;
464 | esac
465 | ;;
466 |
467 | "$ctrl_f"?|~6) # Ctrl-f / PageDown
468 | move=$(( width / 4 > 2 ? width / 4 : 2 ))
469 | # Terminal condition
470 | case $y in
471 | "$last") continue ;;
472 | esac
473 | # Adjust move step
474 | case $(( y - last + move )) in
475 | [1-9]*) move=$(( last - y )) ;;
476 | esac
477 | # Move or update list
478 | case $(( yy - len + move )) in
479 | -*|0) term_move_down "$@" ;;
480 | *) yy=1; return 0 ;;
481 | esac
482 | ;;
483 | k?|A2|"$ctrl_p"?) # k / Arrow Up / Ctrl-p
484 | move=1
485 | # Terminal condition
486 | case $y in
487 | -*|0|1) continue ;;
488 | esac
489 | # Move or update list
490 | case $(( yy - move )) in
491 | -*|0) yy=$len; return 0 ;;
492 | *) term_move_up "$@" ;;
493 | esac
494 | ;;
495 | j?|B2|"$ctrl_n"?) # j / Arrow Down / Ctrl-n
496 | move=1
497 | # Terminal condition
498 | case $y in
499 | "$last") continue ;;
500 | esac
501 | # Move or update list
502 | case $(( yy - len + move )) in
503 | -*|0) term_move_down "$@" ;;
504 | *) yy=1; return 0 ;;
505 | esac
506 | ;;
507 |
508 | g?|H2|"$ctrl_a"?) # g / Home / Ctrl-a
509 | # Terminal condition
510 | case $y in
511 | 1) continue ;;
512 | esac
513 | # Normal mode v.s. Search mode
514 | case $token in
515 | '/')
516 | IFS=${ifs:=$nl}
517 | # shellcheck disable=2086
518 | set -- $filt_out
519 | unset IFS
520 | ;;
521 | '')
522 | IFS=${ifs:=$nl}
523 | # shellcheck disable=2086
524 | set -- $content
525 | unset IFS
526 | esac
527 | y=1 yy=1 y2=$top pos=$top
528 | redraw "$@"
529 | return 0
530 | ;;
531 | G?|F2|"$ctrl_e"?) # G / End / Ctrl-e
532 | # Terminal condition
533 | case $y in
534 | "$last") continue ;;
535 | esac
536 | # Normal mode v.s. Search mode
537 | case $token in
538 | '/')
539 | IFS=${ifs:=$nl}
540 | # shellcheck disable=2086
541 | set -- $filt_out
542 | unset IFS
543 | ;;
544 | '')
545 | IFS=${ifs:=$nl}
546 | # shellcheck disable=2086
547 | set -- $content
548 | unset IFS
549 | esac
550 | y=$last yy=$len pos=$(( num * y - num + top ))
551 | y2=$(( pos < fin - (num - 1) ? pos : fin - (num - 1) ))
552 | redraw "$@"
553 | return 0
554 | ;;
555 | l?|C2|"$esc") # l / Arrow Right / Esc
556 | case $token in
557 | '?') continue ;;
558 | *)
559 | term_reset
560 | return 1
561 | ;;
562 | esac
563 | ;;
564 |
565 | h?|D2|"$bs_char"?) # h / Arrow Left / Backspace
566 | case $token in
567 | '?'|'/')
568 | unset token filt_out ans
569 | old_reset
570 | IFS=${ifs:=$nl}
571 | # shellcheck disable=2086
572 | set -- $content
573 | unset IFS
574 | y=1 yy=1 y2=$top
575 | redraw "$@"
576 | return 0
577 | ;;
578 | esac
579 | ;;
580 |
581 | /?) # /
582 | count=0 bound=$width
583 | old_save
584 | while :; do
585 | case $live_search in
586 | '')
587 | token='/'
588 | prompt / r
589 | ;;
590 | 1)
591 | token='l'
592 | prompt "/$ans" l
593 | case $press${esc:=0} in
594 | "$esc") # Enter
595 | case $cur in
596 | 'no result') continue ;;
597 | *)
598 | ltype="Searching..."
599 | status_line "$last"
600 | ltype="Search mode"
601 | token='/'
602 | case $# in
603 | 1) # only one result, directly output it
604 | term_reset
605 | return 1
606 | ;;
607 | esac
608 | ;;
609 | esac
610 | ;;
611 | "$bs_char"?) # Backspace
612 | case $ans in
613 | '') continue ;; # Inactive when no ans
614 | *)
615 | ans=${ans%%?}
616 | count=0
617 | unset complist
618 | ;;
619 | esac
620 | ;;
621 | "$tab"?) # Tab
622 | case $count in
623 | 0) # First run, record complist & $#
624 | complist=$filt_out
625 | IFS=${ifs:=$nl}
626 | # shellcheck disable=2086
627 | set -- $complist
628 | unset IFS
629 | tot=$#
630 | count=$(( count + 1 <= $# ? count + 1 : $# ))
631 | tab_complete "$@"
632 | ;;
633 | *) # Other run, compare count and $#
634 | case $(( count - tot )) in
635 | -*)
636 | IFS=${ifs:=$nl}
637 | # shellcheck disable=2086
638 | set -- $complist
639 | unset IFS
640 | count=$(( count + 1 <= $# ? count + 1 : $# ))
641 | tab_complete "$@"
642 | ;;
643 | *) continue ;;
644 | esac
645 | ;;
646 | esac
647 | ;;
648 | Z2) # Shift-Tab
649 | case $count in
650 | 0) # First run, record complist
651 | complist=$filt_out
652 | IFS=${ifs:=$nl}
653 | # shellcheck disable=2086
654 | set -- $complist
655 | unset IFS
656 | count=$(( count - 1 >= 1 ? count - 1 : 1 ))
657 | tab_complete "$@"
658 | esc=0
659 | ;;
660 | *) # Other run, compare count and 1
661 | case $(( count - 1 )) in
662 | -*|0) esc=0; continue ;;
663 | *)
664 | IFS=${ifs:=$nl}
665 | # shellcheck disable=2086
666 | set -- $complist
667 | unset IFS
668 | count=$(( count - 1 >= 1 ? count - 1 : 1 ))
669 | tab_complete "$@"
670 | esc=0
671 | ;;
672 | esac
673 | ;;
674 | esac
675 | ;;
676 | "$esc_c"*) esc=1; continue ;;
677 | '[1') esc=2; continue ;;
678 | [[:cntrl:]]?) esc=0; continue ;; # Do not accept other control char
679 | *?) # Others
680 | esc=0
681 | ans="$ans$press"
682 | count=0
683 | unset complist
684 | ;;
685 | esac
686 | ;;
687 | esac
688 | search "$content" "$ifs" "$ans"
689 | # redraw if different & not Enter
690 | [ "$filt_out" = "$last_filt_out" ] && [ -n "$press" ] && continue
691 | IFS=${ifs:=$nl}
692 | # shellcheck disable=2086
693 | set -- $filt_out
694 | unset IFS
695 | msg="Search by *${ans%%$nl*}*";
696 | case $# in
697 | 0) num=1; set -- 'no result' ;;
698 | esac
699 | case $token in
700 | l)
701 | last='??'
702 | last_filt_out="$filt_out"
703 | redraw "$@"
704 | ;;
705 | /)
706 | last=$#
707 | len=$(( $# > 500 ? 500 : $# ))
708 | # num=$old_num
709 | y=1 yy=1 y2=$top pos=$top
710 | term_getsize
711 | term_setup
712 | redraw "$@"
713 | break
714 | ;;
715 | esac
716 | done
717 | unset count complist bound last_filt_out
718 | return 0
719 | ;;
720 |
721 | \??) # ?
722 | set -- 'k/↑/Ctrl-p - up' \
723 | 'j/↓/Ctrl-n - down' \
724 | 'l/→ - right' \
725 | 'h/← - left' \
726 | 'Ctrl-f/PageDown - PageDown' \
727 | 'Ctrl-u/PageUp - PageUp' \
728 | 'g/Home/Ctrl-a - go to top' \
729 | 'G/End/Ctrl-e - go to bottom' \
730 | '/ - search' \
731 | ' live-search detail:' \
732 | ' Enter - confirm' \
733 | ' Backspace - delete previous character' \
734 | ' Tab - Tab-completion forward' \
735 | ' Shift-Tab - Tab-completion backward' \
736 | ' control char ignore' \
737 | ' others print out' \
738 | '? - show keybinds' \
739 | 'q - quit'
740 |
741 | unset format
742 | old_save
743 | num=1; y=1; yy=1; y2=$top; last=$#; len=$#; ltype=""; msg=keybinds; token='?';
744 | redraw "$@"
745 | ;;
746 |
747 | q?) # q
748 | term_exit
749 | exit 0
750 | ;;
751 | "$esc_c"*) esc=1 ;;
752 | '[1') esc=2 ;;
753 | 5?) esc=5 ;; # PageUp
754 | 6?) esc=6 ;; # PageDown
755 | *) esc=0 ;;
756 | esac
757 | done
758 | }
759 |
760 | main() {
761 |
762 | set -e
763 |
764 | trap 'term_exit; exit 0' INT QUIT
765 | trap 'term_getsize; term_setup; y=1; yy=1; y2=$top; pos=$top; redraw "$@"' WINCH
766 |
767 | term_getsize
768 | term_init
769 | y=1 yy=1 y2=$top pos=$top
770 |
771 | IFS=${ifs:=$nl}
772 | # shellcheck disable=2086
773 | set -- $content
774 | unset IFS
775 | last=$#
776 | len=$(( $# > 500 ? 500 : $# ))
777 |
778 | input_assign "0" "$len" "$@"
779 |
780 | IFS=${ifs:=$nl}
781 | # shellcheck disable=2086
782 | set -- $list
783 | unset IFS
784 |
785 | redraw "$@"
786 |
787 | # shellcheck disable=2181
788 | while key "$@"; do
789 | case $token in
790 | '/') # Search mode
791 | IFS=${ifs:=$nl}
792 | # shellcheck disable=2086
793 | set -- $filt_out
794 | unset IFS
795 | ;;
796 | '') # Normal
797 | IFS=${ifs:=$nl}
798 | # shellcheck disable=2086
799 | set -- $content
800 | unset IFS
801 | esac
802 | done
803 | }
804 |
805 | usage () {
806 | cat << EOF
807 | Usage:
808 |
809 | shellect [OPTIONS] ([ARGS])
810 |
811 | -h, Show help options
812 | -i, Set case-insensitive search
813 | -l, Set live-search
814 | -n=[num], Set numbers of line per entry
815 | -d=[delim], Set delimiter (IFS, internal field separator)
816 | -c=[content], Set content to display
817 | -f=[format], Set the format to print out content
818 | -t=[msg], Set top status bar message
819 | -b=[msg], Set bottom status bar message
820 |
821 | format detail:
822 | nldel delete last nl, equiv to "\${1%\$nl}"
823 | basename only print basename, equiv to "\${1##*/}" ;;
824 |
825 | if unset or empty, then equiv to "\$1"
826 |
827 | live-search detail:
828 | Enter confirm
829 | Backspace delete previous character
830 | Tab Tab-completion forward
831 | Shift-Tab Tab-completion backward
832 | control char ignore
833 | others print out
834 | EOF
835 | }
836 |
837 |
838 | nl='
839 | '
840 | tab=' '
841 |
842 | # special key setting
843 | esc_c=$(printf '\033')
844 | bs_char=$(printf '\177')
845 | ctrl_f=$(printf '\006')
846 | ctrl_u=$(printf '\025')
847 | ctrl_n=$(printf '\016')
848 | ctrl_p=$(printf '\020')
849 | ctrl_a=$(printf '\001')
850 | ctrl_e=$(printf '\005')
851 |
852 | while getopts "t:b:n:d:c:f:hil" result; do
853 | case "${result}" in
854 | n) num=${OPTARG} ;;
855 | d) ifs=${OPTARG} ;;
856 | c) content=${OPTARG} ;;
857 | f) format=${OPTARG} ;;
858 | i) case_insense=1 ;;
859 | l) live_search=1 ;;
860 | t) msg="${OPTARG}" ;;
861 | b) ltype="${OPTARG}" ;;
862 | h) usage && exit 0 ;;
863 | *) printf 'Invalid argument' && exit 0 ;;
864 | esac
865 | done
866 |
867 | case "$content" in
868 | '') content=$(cat -u -) ;; # Accept pipe stdin
869 | esac
870 |
871 | num=${num:=1} # num=1 if unset
872 | main <&2 >/dev/tty # why it works, I don't know.
873 | printf '%s' "$cur" >&1
874 |
--------------------------------------------------------------------------------