ansi-select 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +16 -11
- data/ansi-select.gemspec +4 -4
- data/bin/ansi-select +4 -2
- data/lib/ansi/selector.rb +21 -0
- data/lib/ansi/selector/impl.rb +133 -0
- data/lib/ansi/selector/multi_impl.rb +29 -0
- data/lib/ansi/selector/single_impl.rb +21 -0
- data/lib/ansi/selector/version.rb +5 -0
- metadata +11 -7
- data/lib/ansi/select.rb +0 -121
- data/lib/ansi/select/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31facd9c408f0836c2f8ab324fc26995701a433d
|
4
|
+
data.tar.gz: f6dc35cd7b1ea27db706eaba87912afe22abb5f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c13549de88f3ddc5905ea2813122140b1b0fec2a58a87a35c911e0fea365181867fa8fd2c8a06be1f4bd0b9668956aa267e3465f7d2e4a545d8b972cc4e2f14a
|
7
|
+
data.tar.gz: df044fe624e21523b04bccc32bf76e7565b055171d0cb946fbf84f68c30ceeecbe5637d2df1c88f20abf127e903e16d1b68653d9bf888c879c29079fed9b5395
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
![](https://dl.dropboxusercontent.com/spa/dlqheu39w0arg9q/
|
1
|
+
![](https://dl.dropboxusercontent.com/spa/dlqheu39w0arg9q/gvpg7_fw.png)
|
2
2
|
|
3
3
|
## Installation
|
4
4
|
|
@@ -13,17 +13,25 @@ There are two options:
|
|
13
13
|
|
14
14
|
```bash
|
15
15
|
echo some words to choose from | tr ' ' '\n' | ansi-select
|
16
|
+
|
16
17
|
cd $(ls -d */ | ansi-select) # Go to a visually selected subdirectory.
|
17
18
|
git checkout $(git branch | ansi-select) # The same, but with git branches.
|
19
|
+
|
20
|
+
rm -r $(ls | ansi-select --multi) # Delete all the selected files.
|
18
21
|
```
|
19
22
|
|
20
23
|
* A Ruby library.
|
21
24
|
|
22
25
|
```ruby
|
23
|
-
require "ansi/
|
26
|
+
require "ansi/selector"
|
27
|
+
|
28
|
+
beverage = Ansi::Selector.select(["coffee", "tee"])
|
29
|
+
|
30
|
+
puts "Would you like some additions?"
|
31
|
+
additions = Ansi::Selector.multi_select(["sugar", "cream", "milk"])
|
24
32
|
|
25
|
-
|
26
|
-
print "
|
33
|
+
print "Here's your #{beverage}. "
|
34
|
+
print "We've also added #{additions.join(', ')}." if additions.present?
|
27
35
|
```
|
28
36
|
|
29
37
|
The Ruby interface has an additional benefit of accepting any objects that respond
|
@@ -32,10 +40,7 @@ to `#to_s` and returning one of them instead of a string.
|
|
32
40
|
|
33
41
|
## Keyboard
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
## TODO
|
40
|
-
|
41
|
-
* Support multi-select.
|
43
|
+
* Up and down arrows or `j`/`k` to move around.
|
44
|
+
* Return to choose.
|
45
|
+
* Space to toggle in multi select mode.
|
46
|
+
* Ctrl-c or `q` to quit.
|
data/ansi-select.gemspec
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'ansi/
|
4
|
+
require 'ansi/selector/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "ansi-select"
|
8
|
-
spec.version = Ansi::
|
8
|
+
spec.version = Ansi::Selector::VERSION
|
9
9
|
spec.authors = ["Volodymyr Shatskyi"]
|
10
10
|
spec.email = ["shockone89@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{This gem allows you to select
|
12
|
+
spec.summary = %q{A simple, not full-screen, ncurses-like TUI selector}
|
13
|
+
spec.description = %q{This gem allows you to select array elements (where an array is some arbitrary input) with a pretty text user interface.}
|
14
14
|
spec.homepage = "https://github.com/shockone/ansi-select"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
data/bin/ansi-select
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'ansi/
|
2
|
+
require 'ansi/selector'
|
3
3
|
|
4
|
-
|
4
|
+
method_name = ARGV.include?('--multi') ? :multi_select : :select
|
5
|
+
|
6
|
+
puts Ansi::Selector.public_send(method_name, STDIN.readlines.map(&:chomp))
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ansi
|
2
|
+
class Selector
|
3
|
+
# @param [Array<#to_s>] options
|
4
|
+
#
|
5
|
+
# @return [#to_s] option
|
6
|
+
def self.select(options)
|
7
|
+
require_relative "selector/single_impl"
|
8
|
+
|
9
|
+
SingleImpl.new(options).select
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Array<#to_s>] options
|
13
|
+
#
|
14
|
+
# @return [Array<#to_s>] option
|
15
|
+
def self.multi_select(options)
|
16
|
+
require_relative "selector/multi_impl"
|
17
|
+
|
18
|
+
MultiImpl.new(options).select
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require "io/console"
|
2
|
+
|
3
|
+
module Ansi
|
4
|
+
class Selector
|
5
|
+
class Impl
|
6
|
+
CODES = {
|
7
|
+
standout_mode: `tput rev`,
|
8
|
+
exit_standout_mode: `tput rmso`,
|
9
|
+
cursor_up: `tput cuu1`,
|
10
|
+
cursor_down: `tput cud1`,
|
11
|
+
carriage_return_key: `tput cr`
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
@options = options
|
16
|
+
|
17
|
+
@highlighted_line_index = 0
|
18
|
+
@cursor_line_index = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def select
|
22
|
+
print_options
|
23
|
+
answer = ask_to_choose
|
24
|
+
go_to_line(@options.size)
|
25
|
+
|
26
|
+
answer
|
27
|
+
ensure
|
28
|
+
tty.close
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @return [File]
|
34
|
+
def tty
|
35
|
+
@tty ||= File.open('/dev/tty', 'w+')
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_options
|
39
|
+
@options.each.with_index do |_, index|
|
40
|
+
print_line(index, index == @highlighted_line_index)
|
41
|
+
|
42
|
+
unless index == @options.size - 1
|
43
|
+
tty.print $/ # This strange thing is a cross-platform new line.
|
44
|
+
@cursor_line_index += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
go_to_line(0)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def listen_carefully_to_keyboard
|
53
|
+
tty.noecho do
|
54
|
+
tty.raw do
|
55
|
+
input = tty.getc.chr
|
56
|
+
if input == "\e"
|
57
|
+
input << tty.read_nonblock(3) rescue nil
|
58
|
+
input << tty.read_nonblock(2) rescue nil
|
59
|
+
end
|
60
|
+
|
61
|
+
input
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [#to_s]
|
67
|
+
def ask_to_choose
|
68
|
+
loop do
|
69
|
+
input = listen_carefully_to_keyboard
|
70
|
+
|
71
|
+
case input
|
72
|
+
when "\u0003", "q"
|
73
|
+
exit(0)
|
74
|
+
when " "
|
75
|
+
space_handler
|
76
|
+
when CODES[:carriage_return_key]
|
77
|
+
break carriage_return_handler
|
78
|
+
when "\e[A", "k", CODES[:cursor_up]
|
79
|
+
highlight_line(@highlighted_line_index - 1) unless @highlighted_line_index == 0
|
80
|
+
when "\e[B", "j", CODES[:cursor_down]
|
81
|
+
highlight_line(@highlighted_line_index + 1) unless @highlighted_line_index == @options.size - 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Fixnum] index
|
87
|
+
# @param [Boolean] highlight
|
88
|
+
def print_line(index, highlight)
|
89
|
+
go_to_line(index)
|
90
|
+
|
91
|
+
if highlight
|
92
|
+
tty.print(CODES[:standout_mode] + prefix(index) + @options[index] + CODES[:exit_standout_mode])
|
93
|
+
else
|
94
|
+
tty.print(prefix(index) + @options[index])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Fixnum] index
|
99
|
+
def highlight_line(index)
|
100
|
+
print_line(@highlighted_line_index, false)
|
101
|
+
print_line(index, true)
|
102
|
+
|
103
|
+
@highlighted_line_index = index
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param [Fixnum] index
|
107
|
+
def go_to_line(index)
|
108
|
+
if index == @cursor_line_index
|
109
|
+
# do nothing
|
110
|
+
elsif index > @cursor_line_index
|
111
|
+
(index - @cursor_line_index).times { tty.print CODES[:cursor_down] }
|
112
|
+
else
|
113
|
+
(@cursor_line_index - index).times { tty.print CODES[:cursor_up] }
|
114
|
+
end
|
115
|
+
|
116
|
+
@cursor_line_index = index
|
117
|
+
tty.print CODES[:carriage_return_key]
|
118
|
+
end
|
119
|
+
|
120
|
+
def prefix(index)
|
121
|
+
raise NotImplementedError
|
122
|
+
end
|
123
|
+
|
124
|
+
def space_handler
|
125
|
+
raise NotImplementedError
|
126
|
+
end
|
127
|
+
|
128
|
+
def carriage_return_handler
|
129
|
+
raise NotImplementedError
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'impl'
|
2
|
+
|
3
|
+
module Ansi
|
4
|
+
class Selector
|
5
|
+
class MultiImpl < Impl
|
6
|
+
def initialize(options)
|
7
|
+
super
|
8
|
+
|
9
|
+
# @type [Array<Boolean>]
|
10
|
+
@selected_options = []
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def prefix(index)
|
16
|
+
@selected_options[index] ? '[x] ' : '[ ] '
|
17
|
+
end
|
18
|
+
|
19
|
+
def space_handler
|
20
|
+
@selected_options[@cursor_line_index] = !@selected_options[@cursor_line_index]
|
21
|
+
print_line(@cursor_line_index, true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def carriage_return_handler
|
25
|
+
@selected_options.map.with_index { |value, index| @options[index] if value }.compact
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'impl'
|
2
|
+
|
3
|
+
module Ansi
|
4
|
+
class Selector
|
5
|
+
class SingleImpl < Impl
|
6
|
+
private
|
7
|
+
|
8
|
+
def prefix(index)
|
9
|
+
' '
|
10
|
+
end
|
11
|
+
|
12
|
+
def space_handler
|
13
|
+
# Do nothing
|
14
|
+
end
|
15
|
+
|
16
|
+
def carriage_return_handler
|
17
|
+
@options[@highlighted_line_index]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ansi-select
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Volodymyr Shatskyi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,8 +38,8 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
-
description: This gem allows you to select
|
42
|
-
input) with a pretty text user interface.
|
41
|
+
description: This gem allows you to select array elements (where an array is some
|
42
|
+
arbitrary input) with a pretty text user interface.
|
43
43
|
email:
|
44
44
|
- shockone89@gmail.com
|
45
45
|
executables:
|
@@ -52,13 +52,17 @@ files:
|
|
52
52
|
- ".ruby-gemset"
|
53
53
|
- ".ruby-version"
|
54
54
|
- ".travis.yml"
|
55
|
+
- CHANGELOG.md
|
55
56
|
- Gemfile
|
56
57
|
- README.md
|
57
58
|
- Rakefile
|
58
59
|
- ansi-select.gemspec
|
59
60
|
- bin/ansi-select
|
60
|
-
- lib/ansi/
|
61
|
-
- lib/ansi/
|
61
|
+
- lib/ansi/selector.rb
|
62
|
+
- lib/ansi/selector/impl.rb
|
63
|
+
- lib/ansi/selector/multi_impl.rb
|
64
|
+
- lib/ansi/selector/single_impl.rb
|
65
|
+
- lib/ansi/selector/version.rb
|
62
66
|
homepage: https://github.com/shockone/ansi-select
|
63
67
|
licenses: []
|
64
68
|
metadata: {}
|
@@ -81,5 +85,5 @@ rubyforge_project:
|
|
81
85
|
rubygems_version: 2.4.8
|
82
86
|
signing_key:
|
83
87
|
specification_version: 4
|
84
|
-
summary:
|
88
|
+
summary: A simple, not full-screen, ncurses-like TUI selector
|
85
89
|
test_files: []
|
data/lib/ansi/select.rb
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
|
-
require "io/console"
|
4
|
-
|
5
|
-
module Ansi
|
6
|
-
class Select
|
7
|
-
CODES = {
|
8
|
-
standout_mode: `tput rev`,
|
9
|
-
exit_standout_mode: `tput rmso`,
|
10
|
-
cursor_up: `tput cuu1`,
|
11
|
-
cursor_down: `tput cud1`,
|
12
|
-
carriage_return_key: `tput cr`
|
13
|
-
}
|
14
|
-
|
15
|
-
# @param [Array<#to_s>] options
|
16
|
-
def initialize(options)
|
17
|
-
@options = options
|
18
|
-
|
19
|
-
@highlighted_line_index = 0
|
20
|
-
@cursor_line_index = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
# @return [#to_s] option
|
24
|
-
def select
|
25
|
-
print_options
|
26
|
-
answer = ask_to_choose
|
27
|
-
go_to_line(@options.size)
|
28
|
-
|
29
|
-
answer
|
30
|
-
ensure
|
31
|
-
tty.close
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
# @return [File]
|
37
|
-
def tty
|
38
|
-
@tty ||= File.open('/dev/tty', 'w+')
|
39
|
-
end
|
40
|
-
|
41
|
-
def print_options
|
42
|
-
@options.each.with_index do |_, index|
|
43
|
-
print_line(index, index == @highlighted_line_index)
|
44
|
-
|
45
|
-
unless index == @options.size - 1
|
46
|
-
tty.print $/ # This strange thing is a cross-platform new line.
|
47
|
-
@cursor_line_index += 1
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
go_to_line(0)
|
52
|
-
end
|
53
|
-
|
54
|
-
# @return [String]
|
55
|
-
def listen_carefully_to_keyboard
|
56
|
-
tty.noecho do
|
57
|
-
tty.raw do
|
58
|
-
input = tty.getc.chr
|
59
|
-
if input == "\e"
|
60
|
-
input << tty.read_nonblock(3) rescue nil
|
61
|
-
input << tty.read_nonblock(2) rescue nil
|
62
|
-
end
|
63
|
-
|
64
|
-
input
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# @return [#to_s]
|
70
|
-
def ask_to_choose
|
71
|
-
loop do
|
72
|
-
input = listen_carefully_to_keyboard
|
73
|
-
|
74
|
-
case input
|
75
|
-
when "\u0003", "q"
|
76
|
-
exit(0)
|
77
|
-
when CODES[:carriage_return_key], " "
|
78
|
-
break @options[@highlighted_line_index]
|
79
|
-
when "\e[A", "k", CODES[:cursor_up]
|
80
|
-
highlight_line(@highlighted_line_index - 1) unless @highlighted_line_index == 0
|
81
|
-
when "\e[B", "j", CODES[:cursor_down]
|
82
|
-
highlight_line(@highlighted_line_index + 1) unless @highlighted_line_index == @options.size - 1
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# @param [Fixnum] index
|
88
|
-
# @param [Boolean] highlight
|
89
|
-
def print_line(index, highlight)
|
90
|
-
go_to_line(index)
|
91
|
-
|
92
|
-
if highlight
|
93
|
-
tty.print "#{CODES[:standout_mode]}#{@options[index]}#{CODES[:exit_standout_mode]}"
|
94
|
-
else
|
95
|
-
tty.print @options[index]
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# @param [Fixnum] index
|
100
|
-
def highlight_line(index)
|
101
|
-
print_line(@highlighted_line_index, false)
|
102
|
-
print_line(index, true)
|
103
|
-
|
104
|
-
@highlighted_line_index = index
|
105
|
-
end
|
106
|
-
|
107
|
-
# @param [Fixnum] index
|
108
|
-
def go_to_line(index)
|
109
|
-
if index == @cursor_line_index
|
110
|
-
# do nothing
|
111
|
-
elsif index > @cursor_line_index
|
112
|
-
(index - @cursor_line_index).times { tty.print CODES[:cursor_down] }
|
113
|
-
else
|
114
|
-
(@cursor_line_index - index).times { tty.print CODES[:cursor_up] }
|
115
|
-
end
|
116
|
-
|
117
|
-
@cursor_line_index = index
|
118
|
-
tty.print CODES[:carriage_return_key]
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|