ansi-select 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-

|
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
|