omnibar 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ac95e6ea1b99df44bf41c79f7a3790b3f579753
4
- data.tar.gz: 3d3b992d0cd08ae4e45f6e6df63b8de5ad9a2af3
3
+ metadata.gz: 513b1e361992c5857eeb37e52f0a0d2c52486d99
4
+ data.tar.gz: da3effe0d3c63a840d6294b97df44f1d0598835b
5
5
  SHA512:
6
- metadata.gz: aad77472a565a347b6b9ae4db75294b9f3976ae515be84e5a17d2f40cc9075c1c96904a732694d838d2d76a237c80912afa2b96fc16866c04e8041d1c5881b06
7
- data.tar.gz: 779c6a126717a08620abf45d43575d32a3900c30119c25c38d5c723a7a6577750b783479527d8799b5c4cd6c175b6a2f7a3534c2f9866b9537d85ecd9401f55b
6
+ metadata.gz: 2b372c0dde29e27c51d2e566ddd7f1a86de1dfe4487e29aff65c5d532882d6f539a6157bc1f791ab3f3bdd04dd70dcd3d7f39bb1ab955465ba5c705a0e5209d7
7
+ data.tar.gz: 4ed39d98bf94668617a15609daf02d1bf023f8a3f7c9759b4edbd41b685ad65adb400f02d6106793f3a7db436eb329c159b43914e1c892dc86d5a6abfbd5f28d
data/README.md CHANGED
@@ -14,7 +14,25 @@ Install as:
14
14
 
15
15
  ## Usage
16
16
 
17
- Run `bin/omnibar` and start typing. Use the arrow keys to select the result you like, and press enter to go!
17
+ Run `omnibar` and start typing. Use the arrow keys to select the result you like, and press enter to go!
18
+
19
+ Read about an optimal omnibar setup in the wiki: [Omnibar & i3](https://github.com/shreve/omnibar/wiki/Omnibar-&-i3)
20
+
21
+ ## Queries
22
+
23
+ Omnibar is powered by queries. These are the built-in ones:
24
+
25
+ | Name | Description |
26
+ |------|-------------|
27
+ | Calculate | Evaluate math expressions (powered by Math module) |
28
+ | Emoji | Search for emojis by name |
29
+ | Spell | Fuzzy search for the correct spelling of a word |
30
+ | System | Run system commands like sleep, reboot, and shutdown |
31
+ | Snippet | Access named user-provided snippets |
32
+ | GitHub | Access user-provided GitHub repos, or quick open a repo |
33
+ | Popular | Access user-provided popular websites |
34
+ | DuckDuckGo | Search via Duck Duck Go |
35
+ | Google | Search via Google |
18
36
 
19
37
  ## Configuration
20
38
 
@@ -30,21 +48,49 @@ end
30
48
 
31
49
  | Config Key | Type | Default |
32
50
  |------------|------|---------|
51
+ | queries | Array | Every class that inherits from Query |
33
52
  | github.repos | Array | `[]` |
53
+ | popular.sites | Array | `[]` |
54
+ | snippets | Hash | `{ 'shrug' => '¯\_(ツ)_/¯' }` |
34
55
  | render.prompt | Lambda / String | `->(width) { ('-' * width) << '>' }` |
35
56
  | render.highlight.fg | Symbol | `:black` |
36
57
  | render.highlight.bg | Symbol | `:yellow` |
58
+ | events.after_start | Lambda | `-> {}` |
37
59
  | events.after_perform | Lambda | `-> {}` |
38
60
 
61
+ Create your own queries by adding the code to your config file. Your class simply needs to extend from `Omnibar::Query`
62
+
63
+ ```ruby
64
+ class MyQuery < Omnibar::Query
65
+ def result
66
+ # The string that should be displayed inside the preview
67
+ # If this is null, the query won't be included
68
+
69
+ input # The current input value
70
+ end
71
+
72
+ def perform!
73
+ # Execute the query
74
+
75
+ copy_to_clipboard input
76
+ open_in_browser "https://#{input}"
77
+ end
78
+ end
79
+ ```
80
+
81
+ If you think other people might like your query, please feel free to submit a pull reqest. I'm open to including any good ideas.
82
+
83
+ If you don't want a query to run, delete it from `Omnibar.queries`.
84
+
39
85
  ## OS Limitations
40
86
 
41
87
  This gem has been developed on Ubuntu, and should work on most linux distributions.
42
88
  All the code is in ruby, but there are several dependencies that are linux-only.
43
89
 
44
- | Library | Usage |
45
- |---------|-------|
46
- | xdg-open | Opening urls and files |
47
- | GNU aspell | Spell checking and suggestions |
90
+ | Library | Usage | Notes |
91
+ |---------|-------|-------|
92
+ | xdg-open | Opening urls and files | |
93
+ | GNU aspell | Spell checking and suggestions | Possibly installable on OS X using homebrew |
48
94
 
49
95
  ## Contributing
50
96
 
@@ -0,0 +1,58 @@
1
+ # This module contains helpers for various ansi-code operations
2
+ module ANSI
3
+ # ANSI color escape codes set the foreground and background colors.
4
+ # Forground color is a number between 30 and 37.
5
+ # Background color is a number between 40 and 47.
6
+ # The ones place represents the same color for both.
7
+ COLORS = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, 'white', nil, :white].freeze
8
+
9
+ def self.clear_screen
10
+ $stdout.write "\e[2J"
11
+ end
12
+
13
+ def self.move_cursor(row, col)
14
+ $stdout.write "\e[#{row + 1};#{col + 1}H"
15
+ end
16
+
17
+ def self.shift_cursor(rows: 0, cols: 0)
18
+ pos = position
19
+ row = pos[:row] + rows
20
+ col = pos[:column] + cols
21
+ move_cursor(row, col)
22
+ end
23
+
24
+ def self.color(text, fg: :white, bg: :black)
25
+ fg = COLORS.index(fg) + 30
26
+ bg = COLORS.index(bg) + 40
27
+ code = "\e[#{[fg, bg].compact.join(';')}m"
28
+ "#{code}#{text}\e[0m"
29
+ end
30
+
31
+ def self.reset
32
+ "\e[0m"
33
+ end
34
+
35
+ def self.position
36
+ res = ''
37
+ $stdin.raw do |stdin|
38
+ $stdout << "\e[6n"
39
+ $stdout.flush
40
+ while (c = stdin.getc) != 'R'
41
+ res << c if c
42
+ end
43
+ end
44
+ m = res.match(/(?<row>\d+);(?<column>\d+)/)
45
+ {
46
+ row: m[:row].to_i,
47
+ column: m[:column].to_i
48
+ }
49
+ end
50
+
51
+ def self.size
52
+ win = IO.console.winsize
53
+ {
54
+ height: win[0],
55
+ width: win[1]
56
+ }
57
+ end
58
+ end
@@ -3,22 +3,20 @@ require 'dry-configurable'
3
3
  require 'fuzzy_match'
4
4
  require 'amatch'
5
5
 
6
- require 'omnibar/version'
7
- require 'omnibar/app'
8
-
9
- require 'omnibar/calculate'
10
- require 'omnibar/emoji'
11
- require 'omnibar/spell'
12
- require 'omnibar/system'
13
- require 'omnibar/snippet'
14
- require 'omnibar/github'
15
- require 'omnibar/popular'
16
- require 'omnibar/duck_duck_go'
17
- require 'omnibar/google'
6
+ require_relative 'ansi'
7
+
8
+ require_relative 'omnibar/version'
9
+ require_relative 'omnibar/app'
10
+ require_relative 'omnibar/query'
11
+ require_relative 'omnibar/renderer'
12
+ require_relative 'omnibar/state'
13
+ require_relative 'omnibar/view'
18
14
 
19
15
  module Omnibar
20
16
  extend Dry::Configurable
21
17
 
18
+ setting :queries, []
19
+
22
20
  setting :github do
23
21
  setting :repos, []
24
22
  end
@@ -27,14 +25,10 @@ module Omnibar
27
25
  setting :sites, []
28
26
  end
29
27
 
30
- setting :spell do
31
- setting :dict, '/usr/share/dict/american-english'
32
- end
33
-
34
28
  setting :snippets, 'shrug' => '¯\_(ツ)_/¯'
35
29
 
36
30
  setting :render do
37
- setting :prompt, ->(width) { ('-' * width) << '>' }
31
+ setting :prompt, ->(width) { (' ' * width) << ANSI.color('', fg: :magenta) }
38
32
 
39
33
  setting :highlight do
40
34
  setting :fg, :black
@@ -43,6 +37,7 @@ module Omnibar
43
37
  end
44
38
 
45
39
  setting :events do
40
+ setting :after_start, ->(app) {}
46
41
  setting :after_perform, -> {}
47
42
  end
48
43
 
@@ -53,3 +48,11 @@ module Omnibar
53
48
  end
54
49
 
55
50
  FuzzyMatch.engine = :amatch
51
+
52
+ # Require all the queries
53
+ dir = File.join(File.dirname(File.expand_path(__FILE__)), 'omnibar/queries')
54
+ Dir["#{dir}/*.rb"].each { |file| require file }
55
+
56
+ # Move fallback queries to the end
57
+ Omnibar.config.queries << Omnibar.config.queries.delete(Omnibar::DuckDuckGo)
58
+ Omnibar.config.queries << Omnibar.config.queries.delete(Omnibar::Google)
@@ -2,25 +2,12 @@
2
2
 
3
3
  require 'io/console'
4
4
 
5
- require_relative 'ansi'
6
- require_relative 'query'
7
- require_relative 'renderer'
8
-
9
5
  module Omnibar
10
6
  class App
11
- attr_accessor :input
12
- attr_accessor :selection
13
-
14
- @@queries = []
15
-
16
- def self.add_query(query)
17
- @@queries.push(query)
18
- end
19
-
20
7
  def initialize
21
- @input = ''
22
- @selection = 0
8
+ reset_state!
23
9
  Omnibar.load_config
10
+ Omnibar.config.events.after_start.call(self)
24
11
  end
25
12
 
26
13
  def run
@@ -35,7 +22,9 @@ module Omnibar
35
22
  end
36
23
 
37
24
  def render
38
- Renderer.new(input, results, selection).render!
25
+ previous = @current_view
26
+ @current_view = View.new(@state)
27
+ Renderer.new(@state).render_diff(previous, @current_view)
39
28
  end
40
29
 
41
30
  def handle_input(prefix = '')
@@ -44,42 +33,36 @@ module Omnibar
44
33
  when "\u0003" # ctrl-c
45
34
  quit
46
35
  when "\u007F" # backspace
47
- self.input = input[0..-2]
36
+ @state.backspace
48
37
  when "\e", "\e["
49
38
  handle_input(char)
50
- when "\e[A"
51
- self.selection = [selection - 1, 0].max
52
- when "\e[B"
53
- self.selection = [selection + 1, visible_queries.count - 1].min
39
+ when "\e[A" # Up Arrow
40
+ @state.select_up
41
+ when "\e[B" # Down Arrow
42
+ @state.select_down
43
+ when "\e[C"
44
+ @state.move_cursor_left
45
+ when "\e[D"
46
+ @state.move_cursor_right
54
47
  when "\e\e"
55
- self.input = ""
48
+ reset_state!
56
49
  when "\r"
57
50
  perform_action!
51
+ reset_state!
58
52
  else
59
- input << char
53
+ @state.add_to_input(char)
60
54
  end
61
55
  end
62
56
 
63
- def results
64
- return [] if input.empty?
65
-
66
- queries.map(&:preview_text).compact
57
+ def reset_state!
58
+ @state = State.new
67
59
  end
68
60
 
69
61
  def perform_action!
70
- visible_queries[selection]&.perform!
71
- self.input = ""
62
+ @state.current_query.perform!
72
63
  Omnibar.config.events.after_perform.call
73
64
  end
74
65
 
75
- def queries
76
- @@queries.map { |q| q.new(input) }
77
- end
78
-
79
- def visible_queries
80
- queries.reject { |q| q.preview_text.nil? }
81
- end
82
-
83
66
  def quit
84
67
  ANSI.clear_screen
85
68
  exit 0
@@ -2,6 +2,7 @@ module Omnibar
2
2
  class Emoji < Query
3
3
 
4
4
  def result
5
+ return if input.empty?
5
6
  key = search.find(input)
6
7
  value = DICTIONARY[key]
7
8
 
@@ -1,33 +1,40 @@
1
1
  module Omnibar
2
2
  class Query
3
-
4
3
  attr_reader :input
5
4
 
6
5
  def initialize(input)
7
- @input = input
6
+ @input = input.strip
8
7
  end
9
8
 
10
9
  def self.inherited(subclass)
11
- Omnibar::App.add_query(subclass)
10
+ Omnibar.config.queries.push(subclass)
12
11
  super(subclass)
13
12
  end
14
13
 
14
+ # TODO: Convert result to class
15
+ # TODO: Allow multiple results per query
15
16
  def preview_text
16
17
  res = result
17
- name = self.class.name.split('::').last
18
- [name, res] unless result.nil? || result.empty?
18
+ name = self.class.name.split('::').last.gsub(/[A-Z]/) { |w| ' ' << w }.strip
19
+ [name, res.strip] unless res.nil? || res.empty?
19
20
  end
20
21
 
21
22
  def result
22
23
  input
23
24
  end
24
25
 
26
+ def perform!; end
27
+
25
28
  def copy_to_clipboard(value)
26
29
  `echo "#{value}" | xsel -i --clipboard`
27
30
  end
28
31
 
29
32
  def open_in_browser(url)
30
- Thread.new { `xdg-open "#{url}" >/dev/null 2>&1` }
33
+ Thread.new { run_silently 'xdg-open', url }
34
+ end
35
+
36
+ def run_silently(*command)
37
+ `#{command.join(' ')} >/dev/null 2>&1`
31
38
  end
32
39
  end
33
40
  end
@@ -1,60 +1,30 @@
1
1
  module Omnibar
2
2
  class Renderer
3
- NON_ASCII_REGEX = /[^\x00-\x7F]/.freeze
4
-
5
- attr_reader :input, :results, :selection
6
-
7
- def initialize(input, results, selection)
8
- @input = input
9
- @results = results
10
- @selection = selection
3
+ def initialize(state)
4
+ @state = state
11
5
  end
12
6
 
13
- def render!
7
+ def render(view)
14
8
  ANSI.clear_screen
15
9
  ANSI.move_cursor(0, 0)
16
- puts input_line
17
- ANSI.move_cursor(1, 0)
18
- results.each.with_index do |result, i|
19
- text = [
20
- lpad(result.first, max_label_length),
21
- rpad(result.last, ANSI.size[:width] - max_label_length - 2)
22
- ].join(': ')
23
-
24
- if i == selection
25
- text = ANSI.color(text,
26
- fg: Omnibar.config.render.highlight.fg,
27
- bg: Omnibar.config.render.highlight.bg)
28
- end
29
- print "#{text}\r\n"
30
- end
31
- ANSI.move_cursor(0, input_line.length)
10
+ print view.render.join("\e[2K \r\n")
11
+ print view.cursor_position
32
12
  end
33
13
 
34
- def input_line
35
- prompt = Omnibar.config.render.prompt
36
- if prompt.respond_to?(:call)
37
- prompt = prompt.call(max_label_length)
38
- end
39
- "#{prompt} #{input}"
40
- end
14
+ def render_diff(previous, current)
15
+ return render(current) if previous.nil?
41
16
 
42
- def rpad(text, length = ANSI.size[:width])
43
- textlength = text.length + non_ascii_chars(text).length
44
- text + (' ' * [0, (length - textlength)].max)
45
- end
46
-
47
- def lpad(text, length = ANSI.size[:width])
48
- textlength = text.length + non_ascii_chars(text).length
49
- (' ' * [0, (length - textlength)].max) + text
50
- end
17
+ lines = [previous.length, current.length].max
18
+ lines.times.each do |i|
19
+ next if previous[i] == current[i]
51
20
 
52
- def max_label_length
53
- @mll ||= results.map(&:first).map(&:length).max || 10
54
- end
21
+ ANSI.move_cursor(i, 0)
22
+ print "\e[2K"
23
+ print current[i]
24
+ end
55
25
 
56
- def non_ascii_chars(string)
57
- string.chars.select { |c| c.match?(NON_ASCII_REGEX) }
26
+ ANSI
27
+ print current.cursor_position
58
28
  end
59
29
  end
60
30
  end
@@ -0,0 +1,53 @@
1
+ module Omnibar
2
+ class State < Struct.new(:input, :selection, :cursor_offset)
3
+ def initialize(values = {})
4
+ self.input = ''
5
+ self.selection = self.cursor_offset = 0
6
+
7
+ values.each_pair do |attr, val|
8
+ send(:"#{attr}=", val)
9
+ end
10
+ end
11
+
12
+ def add_to_input(string)
13
+ self.input = input.insert(input.length - cursor_offset, string)
14
+ end
15
+
16
+ def backspace
17
+ input.slice!(input.length - 1 - cursor_offset)
18
+ end
19
+
20
+ def select_up
21
+ self.selection = [selection - 1, 0].max
22
+ end
23
+
24
+ def select_down
25
+ self.selection = [selection + 1, visible_queries.count - 1].min
26
+ end
27
+
28
+ def move_cursor_left
29
+ self.cursor_offset = [0, cursor_offset - 1].max
30
+ end
31
+
32
+ def move_cursor_right
33
+ self.cursor_offset = [input.length, cursor_offset + 1].min
34
+ end
35
+
36
+ def queries
37
+ Omnibar.config.queries.map { |q| q.new(input) }
38
+ end
39
+
40
+ def visible_queries
41
+ (@qc ||= {})[input] ||= queries.reject { |q| q.preview_text.nil? }
42
+ end
43
+
44
+ def current_query
45
+ visible_queries[selection] || Query.new
46
+ end
47
+
48
+ # TODO: Sort results based on relevance / certainty
49
+ def results
50
+ visible_queries.map(&:preview_text)
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Omnibar
2
- VERSION = '0.0.5'.freeze
2
+ VERSION = '0.0.6'.freeze
3
3
  end
@@ -0,0 +1,69 @@
1
+ module Omnibar
2
+ class View
3
+ NON_ASCII_REGEX = /[^\x00-\x7F]/.freeze
4
+
5
+ def initialize(state)
6
+ @state = state
7
+ end
8
+
9
+ def [](line)
10
+ render[line]
11
+ end
12
+
13
+ def length
14
+ render.length
15
+ end
16
+
17
+ def render
18
+ @render ||= [
19
+ input_line,
20
+ *rendered_queries
21
+ ]
22
+ end
23
+
24
+ def input_line
25
+ prompt = Omnibar.config.render.prompt
26
+ prompt = prompt.call(max_label_length) if prompt.respond_to?(:call)
27
+ "#{prompt} #{@state.input}"
28
+ end
29
+
30
+ def cursor_position
31
+ "\e[0;#{max_label_length + @state.input.length + 3 - @state.cursor_offset}H"
32
+ end
33
+
34
+ def rendered_queries
35
+ @state.results.map.with_index do |result, i|
36
+ text = [
37
+ lpad(result.first, max_label_length),
38
+ rpad(result.last, ANSI.size[:width] - max_label_length - 2)
39
+ ].join(': ')
40
+
41
+ if i == @state.selection
42
+ text = ANSI.color(text,
43
+ fg: Omnibar.config.render.highlight.fg,
44
+ bg: Omnibar.config.render.highlight.bg)
45
+ end
46
+
47
+ text
48
+ end
49
+ end
50
+
51
+ def rpad(text, length = ANSI.size[:width])
52
+ textlength = text.length + non_ascii_chars(text).length
53
+ text + (' ' * [0, (length - textlength)].max)
54
+ end
55
+
56
+ def lpad(text, length = ANSI.size[:width])
57
+ textlength = text.length + non_ascii_chars(text).length
58
+ (' ' * [0, (length - textlength)].max) + text
59
+ end
60
+
61
+ def max_label_length
62
+ @mll ||= (@state.results.map(&:first).map(&:length).max || 0) + 1
63
+ end
64
+
65
+ def non_ascii_chars(string)
66
+ string.chars.select { |c| c.match?(NON_ASCII_REGEX) }
67
+ end
68
+ end
69
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnibar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Evan Shreve
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-04 00:00:00.000000000 Z
11
+ date: 2017-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -123,21 +123,23 @@ files:
123
123
  - bin/console
124
124
  - bin/setup
125
125
  - exe/omnibar
126
+ - lib/ansi.rb
126
127
  - lib/omnibar.rb
127
- - lib/omnibar/ansi.rb
128
128
  - lib/omnibar/app.rb
129
- - lib/omnibar/calculate.rb
130
- - lib/omnibar/duck_duck_go.rb
131
- - lib/omnibar/emoji.rb
132
- - lib/omnibar/github.rb
133
- - lib/omnibar/google.rb
134
- - lib/omnibar/popular.rb
129
+ - lib/omnibar/queries/calculate.rb
130
+ - lib/omnibar/queries/duck_duck_go.rb
131
+ - lib/omnibar/queries/emoji.rb
132
+ - lib/omnibar/queries/github.rb
133
+ - lib/omnibar/queries/google.rb
134
+ - lib/omnibar/queries/popular.rb
135
+ - lib/omnibar/queries/snippet.rb
136
+ - lib/omnibar/queries/spell.rb
137
+ - lib/omnibar/queries/system.rb
135
138
  - lib/omnibar/query.rb
136
139
  - lib/omnibar/renderer.rb
137
- - lib/omnibar/snippet.rb
138
- - lib/omnibar/spell.rb
139
- - lib/omnibar/system.rb
140
+ - lib/omnibar/state.rb
140
141
  - lib/omnibar/version.rb
142
+ - lib/omnibar/view.rb
141
143
  - omnibar.gemspec
142
144
  homepage: https://github.com/shreve/omnibar
143
145
  licenses:
@@ -1,37 +0,0 @@
1
- module Omnibar
2
- # This module contains helpers for various ansi-code operations
3
- module ANSI
4
- # ANSI color escape codes set the foreground and background colors.
5
- # Forground color is a number between 30 and 37.
6
- # Background color is a number between 40 and 47.
7
- # The ones place represents the same color for both.
8
- COLORS = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, 'white', nil, :white].freeze
9
-
10
- def self.clear_screen
11
- $stdout.write "\e[2J"
12
- end
13
-
14
- def self.move_cursor(row, col)
15
- $stdout.write "\e[#{row + 1};#{col + 1}H"
16
- end
17
-
18
- def self.color(text, fg: :white, bg: :black)
19
- fg = COLORS.index(fg) + 30
20
- bg = COLORS.index(bg) + 40
21
- code = "\e[#{[fg, bg].compact.join(';')}m"
22
- "#{code}#{text}\e[0m"
23
- end
24
-
25
- def self.reset
26
- "\e[0m"
27
- end
28
-
29
- def self.size
30
- win = IO.console.winsize
31
- {
32
- height: win[0],
33
- width: win[1]
34
- }
35
- end
36
- end
37
- end