topinambour 1.0.2 → 1.0.3

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: 2f7932d9b9a4f36c2f645f4d26e55d9b52a994b7
4
- data.tar.gz: e7866495e480919ef65ec5b5db1b390736def36c
3
+ metadata.gz: ebb72186258606bea34685031717fda37dd0e0c3
4
+ data.tar.gz: 5f9126dbeb67655424c77ab18a5ccbee00a61c85
5
5
  SHA512:
6
- metadata.gz: 72feed7bf6215d8cfd3cf937ec0e0b0124300327b4b2b2656d33d9e5feec097661a41b8ef03b929d45e6b602321772676492b0aa2c7333ba0f5aee511ea620b4
7
- data.tar.gz: d1bb51e53e5bccab717f675eedc4f2f3cae0cdcfe3b64f5ab8f650acd42cbb6cab4724b9d86e3fecd45d0434589dd5ecd1d5e8a9ef6e05b0f1086920e8968e65
6
+ metadata.gz: fdf0c7271408c88ec111d7fe5a05571736ef9251bf6a1a5dbde356ce9c3325850a6f5e7162edad4c0ce170e6cf5c59880878cde87d4d902c3b92e766797a7055
7
+ data.tar.gz: cb63168c754ccf4501d3a833e60602a8ebdd3b3ec04769fad5ef891eb7e892bb4f634149745e5e79646a534b92229b0abcb42c745d7bba1a187a5b22a55faf12
data/bin/topinambour CHANGED
@@ -28,7 +28,7 @@ CURRENT_PATH = File.expand_path(File.dirname(__FILE__))
28
28
  ROOT_PATH = File.expand_path("#{CURRENT_PATH}/..")
29
29
  LIB_PATH = "#{CURRENT_PATH}/../lib"
30
30
  DATA_PATH = "#{CURRENT_PATH}/../data"
31
- CONFIG_DIR = File.expand_path("#{ENV['HOME']}/.config/Topinambour")
31
+ CONFIG_DIR = File.expand_path("#{ENV['HOME']}/.config/topinambour")
32
32
  USR_CSS = "#{CONFIG_DIR}/topinambour.css"
33
33
  USR_LIB_PATH = "#{CONFIG_DIR}/lib"
34
34
  gresource_bin = "/tmp/topinambour.gresource"
@@ -46,7 +46,7 @@ end
46
46
  resource = Gio::Resource.load(gresource_bin)
47
47
  Gio::Resources.register(resource)
48
48
  # Load default libraries !!WARNING!! loading order matters
49
- %w(actions style_properties application terminal notebook color_selector font_selector
49
+ %w(actions style_properties css_handler application terminal_regex terminal notebook color_selector font_selector
50
50
  terminal_chooser headerbar shortcuts window resize_message css_editor).each do |l|
51
51
  if File.exist?("#{USR_LIB_PATH}/#{l}.rb")
52
52
  require "#{USR_LIB_PATH}/#{l}.rb"
data/lib/application.rb CHANGED
@@ -15,14 +15,16 @@
15
15
  # along with Topinambour. If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
17
  class TopinambourApplication < Gtk::Application
18
- attr_reader :provider
18
+ attr_reader :provider, :css_file, :css_content # :css_content really needed ?
19
19
  def initialize
20
20
  super("com.github.cedlemo.topinambour", :non_unique)
21
21
 
22
22
  signal_connect "startup" do |application|
23
23
  load_css_config
24
24
  screen = Gdk::Display.default.default_screen
25
- Gtk::StyleContext.add_provider_for_screen(screen, @provider, Gtk::StyleProvider::PRIORITY_USER)
25
+ Gtk::StyleContext.add_provider_for_screen(screen,
26
+ @provider,
27
+ Gtk::StyleProvider::PRIORITY_USER)
26
28
  TopinambourActions.add_actions_to(application)
27
29
  load_menu_ui_in(application)
28
30
  end
@@ -35,88 +37,68 @@ class TopinambourApplication < Gtk::Application
35
37
  end
36
38
  end
37
39
 
38
- def update_css(new_props = nil)
39
- css_properties
40
- @props.merge!(new_props) if new_props
41
- css = update_css_properties
42
- merged_css = Sass::Engine.new(css, :syntax => :scss).render
43
- if File.exist?(USR_CSS)
44
- FileUtils.mv(USR_CSS, "#{USR_CSS}_#{Time.new.strftime('%Y-%m-%d-%H-%M-%S')}.backup")
45
- File.open(USR_CSS, "w") do |file|
46
- file.puts merged_css
47
- end
48
- else
49
- File.open(USR_CSS, "w") do |file|
50
- file.puts merged_css
51
- end
40
+ def update_css(new_props)
41
+ props = []
42
+ new_props.each do |prop|
43
+ props << {:name => prop[0], :value => prop[1]}
44
+ end
45
+ new_css = CssHandler.update_css(@css_file, props)
46
+ replace_old_conf_with(new_css)
47
+
48
+ begin
49
+ load_custom_css_config
50
+ rescue
51
+ puts "Bad css file using default css"
52
+ load_default_css_config
52
53
  end
53
54
  end
54
55
 
55
56
  private
56
57
 
58
+ def replace_old_conf_with(new_conf)
59
+ if File.exist?(USR_CSS)
60
+ new_name = "#{USR_CSS}_#{Time.new.strftime('%Y-%m-%d-%H-%M-%S')}.backup"
61
+ FileUtils.mv(USR_CSS, new_name)
62
+ end
63
+ check_and_create_if_no_config_dir
64
+ File.open(USR_CSS, "w") do |file|
65
+ file.puts new_conf
66
+ end
67
+ end
68
+
57
69
  def load_menu_ui_in(application)
58
70
  builder = Gtk::Builder.new(:resource => "/com/github/cedlemo/topinambour/app-menu.ui")
59
71
  app_menu = builder["appmenu"]
60
72
  application.app_menu = app_menu
61
73
  end
62
74
 
75
+ def load_default_css_config
76
+ @css_content = Gio::Resources.lookup_data("/com/github/cedlemo/topinambour/topinambour.css", 0)
77
+ @css_file = "#{DATA_PATH}/topinambour.css"
78
+ @provider.load(:data => @css_content)
79
+ end
80
+
81
+ def load_custom_css_config
82
+ @css_content = File.open(USR_CSS, "r").read
83
+ @css_file = USR_CSS
84
+ @provider.load(:data => @css_content)
85
+ end
86
+
63
87
  def load_css_config
64
88
  @provider = Gtk::CssProvider.new
65
- default_css = Gio::Resources.lookup_data("/com/github/cedlemo/topinambour/topinambour.css", 0)
66
89
  if File.exist?(USR_CSS)
67
90
  begin
68
- @provider.load(:path => USR_CSS)
91
+ load_custom_css_config
69
92
  rescue
70
93
  puts "Bad css file using default css"
71
- @provider.load(:data => default_css)
94
+ load_default_css_config
72
95
  end
73
96
  else
74
97
  puts "No custom CSS, using default css"
75
- @provider.load(:data => default_css)
98
+ load_default_css_config
76
99
  end
77
100
  end
78
101
 
79
- def load_css_to_tree
80
- engine = Sass::Engine.new(@provider.to_s, :syntax => :scss)
81
- engine.to_tree
82
- end
83
-
84
- def update_css_properties
85
- modified_sass = change_existing_properties
86
- sass_to_add = @props.empty? ? "" : add_new_css_properties
87
- Sass::Engine.new(sass_to_add + modified_sass, :syntax => :sass).render
88
- end
89
-
90
- def add_new_css_properties
91
- new_sass = "*"
92
- @props.each do |k, v|
93
- new_sass += "\n #{k}: #{v}"
94
- end
95
- new_sass + "\n"
96
- end
97
-
98
- def change_existing_properties
99
- keys_found = []
100
- tree = load_css_to_tree
101
- # we search for properties that are already configured
102
- tree.children.each do |node|
103
- node.each do |prop|
104
- next if prop.class != Sass::Tree::PropNode
105
- name = prop.name[0]
106
- next unless @props[name]
107
- keys_found << name unless keys_found.include?(name)
108
- if @props[name] != prop.value.value
109
- value_object = prop.value.value.class.new(@props[name])
110
- prop.value = Sass::Script::Tree::Literal.new(value_object)
111
- end
112
- end
113
- end
114
- keys_found.each do |k|
115
- @props.delete(k)
116
- end
117
- tree.to_sass
118
- end
119
-
120
102
  def css_properties
121
103
  @props = {}
122
104
  return @props if windows[0].notebook.current.class == TopinambourTerminal
@@ -128,4 +110,8 @@ class TopinambourApplication < Gtk::Application
128
110
  @props["-TopinambourTerminal-font"] = DEFAULT_TERMINAL_FONT
129
111
  @props["-TopinambourWindow-shell"] = "\'/usr/bin/fish\'"
130
112
  end
113
+
114
+ def check_and_create_if_no_config_dir
115
+ Dir.mkdir(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
116
+ end
131
117
  end
@@ -37,7 +37,7 @@ class TopinambourColorSelector < Gtk::Box
37
37
  private
38
38
 
39
39
  def initialize_default_colors
40
- @default_colors = @window.notebook.current.get_css_colors
40
+ @default_colors = @window.notebook.current.css_colors
41
41
  @colors = @default_colors.dup
42
42
  end
43
43
 
@@ -67,7 +67,9 @@ class TopinambourColorSelector < Gtk::Box
67
67
  button.signal_connect "clicked" do |widget|
68
68
  new_props = {}
69
69
  TERMINAL_COLOR_NAMES.each_with_index do |c, i|
70
- new_props["-TopinambourTerminal-#{c}"] = @colors[i].to_s
70
+ if @colors[i] != @default_colors[i]
71
+ new_props["-TopinambourTerminal-#{c}"] = @colors[i].to_s
72
+ end
71
73
  end
72
74
  apply_new_css_properties(widget.toplevel, new_props)
73
75
  button.toplevel.exit_overlay_mode
data/lib/css_editor.rb CHANGED
@@ -147,7 +147,7 @@ class TopinambourCssEditor < Gtk::Grid
147
147
  def reload_terminal_custom_css_properties
148
148
  @window.notebook.each do |tab|
149
149
  next unless tab.class == TopinambourTerminal
150
- colors = tab.get_css_colors unless colors
150
+ colors = tab.css_colors unless colors
151
151
  tab.colors = colors
152
152
  tab.apply_colors
153
153
  end
data/lib/css_handler.rb CHANGED
@@ -1,6 +1,26 @@
1
- require "sass"
2
- require "gtk3" # to remove
1
+ # Copyright 2016 Cédric LE MOIGNE, cedlemo@gmx.com
2
+ # This file is part of Topinambour.
3
+ #
4
+ # Topinambour is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # any later version.
8
+ #
9
+ # Topinambour is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Topinambour. If not, see <http://www.gnu.org/licenses/>.
3
16
 
17
+ ##
18
+ # This module contains all the methods needed to read, parse
19
+ # and modify css file.
20
+ # Sass is used to read, parse and get informations about
21
+ # css selectors or properties
22
+ # The modifying or addition of css properties are done
23
+ # with ruby strings facilities
4
24
  module CssHandler
5
25
  def self.css_file?(filename)
6
26
  filename =~ /^.*\.css$/ ? true : false
@@ -38,6 +58,10 @@ module CssHandler
38
58
  end
39
59
  end
40
60
 
61
+ def self.reload_engine(engine, css_content)
62
+ Sass::Engine.new(css_content, :syntax => engine.options[:syntax])
63
+ end
64
+
41
65
  def self.color_property?(value)
42
66
  parsed_color = nil
43
67
  begin
@@ -50,11 +74,11 @@ module CssHandler
50
74
 
51
75
  def self.property_to_css_instructions(name, value)
52
76
  if value.class == String && color_property?(value)
53
- "#{name}: #{value};\n"
77
+ "#{name}: #{value}"
54
78
  elsif value.class == String
55
- "#{name}: \"#{value}\";\n"
79
+ %(#{name}: "#{value}")
56
80
  else
57
- "#{name}: #{value};\n"
81
+ "#{name}: #{value}"
58
82
  end
59
83
  end
60
84
 
@@ -69,7 +93,7 @@ module CssHandler
69
93
  end
70
94
  props
71
95
  end
72
-
96
+
73
97
  def self.prop_position(prop)
74
98
  source_range = prop.source_range
75
99
  [source_range.start_pos, source_range.end_pos]
@@ -79,43 +103,134 @@ module CssHandler
79
103
  tree.children.each do |node|
80
104
  node.each do |prop|
81
105
  next if prop.class != Sass::Tree::PropNode
106
+ next if prop.class == Sass::Tree::CommentNode
82
107
  return true if prop.name[0] == name
83
108
  end
84
109
  end
85
110
  false
86
111
  end
87
112
 
88
- def self.modify_property(filename, tree, prop)
89
- # Get the property line/pos via the tree
90
- # modify the existing prop in the file
91
- # create a temp file
92
- # copy initial file and modify the part we want
93
- # replace initial file with temp file
113
+ def self.modify_in_ranges(line, line_number, start_range, end_range, value)
114
+ if line_number < start_range.line || line_number > end_range.line
115
+ line
116
+ else
117
+ tmp = ""
118
+ if line_number == start_range.line
119
+ tmp += line[0..(start_range.offset - 2)] + value
120
+ end
121
+ tmp += line[(end_range.offset - 1)..-1] if line_number == end_range.line
122
+ tmp
123
+ end
124
+ end
125
+
126
+ def self.modify_property_value(file_content, property, value)
127
+ start_range = property.value_source_range.start_pos
128
+ end_range = property.value_source_range.end_pos
129
+ tmp = ""
130
+ line_number = 1
131
+ file_content.each_line do |line|
132
+ tmp += modify_in_ranges(line, line_number, start_range, end_range, value)
133
+ line_number += 1
134
+ end
135
+ tmp
136
+ end
137
+
138
+ def self.modify_each_property_values(css_content, engine, prop)
139
+ engine = engine
140
+ tree = engine.to_tree
141
+ props = props_with_name(tree, prop[:name])
142
+ new_css = css_content
143
+ (0..(props.size - 1)).each do |i|
144
+ new_css = modify_property_value(new_css, props[i], prop[:value])
145
+ engine = reload_engine(engine, new_css)
146
+ tree = engine.to_tree
147
+ props = props_with_name(tree, prop[:name])
148
+ end
149
+ new_css
150
+ end
151
+
152
+ def self.append_new_property_after_line(line, prop, indent)
153
+ tmp = line.gsub(/\;[^\;]*$/, ";\n")
154
+ new_prop = property_to_css_instructions(prop[:name], prop[:value])
155
+ tmp += (indent + new_prop + (Regexp.last_match(0) || ";\n"))
156
+ tmp
157
+ end
158
+
159
+ def self.last_child_which_is_not_comment(selector)
160
+ son = nil
161
+ selector.children.each do |child|
162
+ son = child unless child.class == Sass::Tree::CommentNode
163
+ end
164
+ son
165
+ end
166
+
167
+ def self.compute_position_to_append(selector, element)
168
+ indent = last_line = nil
169
+ if element
170
+ indent = " " * (element.name_source_range.start_pos.offset - 1) || ""
171
+ last_line = element.value_source_range.end_pos.line
172
+ else # If we don 't have any property in selector, use sel offset
173
+ indent = " " * (selector.source_range.start_pos.offset - 1) || ""
174
+ last_line = selector.source_range.start_pos.line
175
+ end
176
+ [indent, last_line]
177
+ end
178
+
179
+ def self.append_property_in_universal_selector(css_content, engine, prop)
180
+ last_selector = selectors_with_name(engine.to_tree, "*").last
181
+ last_prop = last_child_which_is_not_comment(last_selector)
182
+ indent, last_line = compute_position_to_append(last_selector, last_prop)
183
+ tmp = ""
184
+ line_number = 1
185
+ css_content.each_line do |line|
186
+ if last_line == line_number
187
+ tmp += append_new_property_after_line(line, prop, indent)
188
+ else
189
+ tmp += line
190
+ end
191
+ line_number += 1
192
+ end
193
+ tmp
94
194
  end
95
195
 
96
- def self.append_property_in_universal_selector(filename, tree, prop)
97
- # Get the universal selector line/pos via the tree
98
- # append our property at the end of this selector
196
+ def self.rule_node_name_is?(node, name)
197
+ if node.rule.include?(name)
198
+ true
199
+ elsif node.parsed_rules.to_s == name
200
+ true
201
+ elsif node.resolved_rules == name
202
+ true
203
+ else
204
+ false
205
+ end
99
206
  end
100
207
 
101
208
  def self.selectors_with_name(tree, sel_name)
102
209
  selectors = []
103
210
  tree.children.each do |node|
104
211
  next unless node.class == Sass::Tree::RuleNode
105
- selectors << node
212
+ selectors << node if rule_node_name_is?(node, sel_name)
106
213
  end
107
214
  selectors
108
- end
109
-
215
+ end
216
+
217
+ def self.update_css_with_new_property(content, engine, prop)
218
+ if property_defined?(engine.to_tree, prop[:name])
219
+ modify_each_property_values(content, engine, prop)
220
+ else
221
+ append_property_in_universal_selector(content, engine, prop)
222
+ end
223
+ end
224
+
110
225
  def self.update_css(filename, properties)
111
226
  engine = to_engine(filename)
112
- tree = engine.to_tree
227
+ new_css = nil
228
+ content = File.open(filename, "r").read
113
229
  properties.each do |prop|
114
- if property_defined?(tree, prop[:name])
115
- modify_property(filename, tree, prop)
116
- else
117
- append_property_in_universal_selector(filename, tree, prop)
118
- end
230
+ new_css = update_css_with_new_property(content, engine, prop)
231
+ engine = reload_engine(engine, new_css)
232
+ content = new_css
119
233
  end
234
+ new_css
120
235
  end
121
236
  end
data/lib/terminal.rb CHANGED
@@ -49,13 +49,8 @@ class TopinambourTerminal < Vte::Terminal
49
49
  signal_connect "child-exited" do |widget|
50
50
  notebook = widget.parent
51
51
  current_page = notebook.page_num(widget)
52
- if notebook.n_pages > 1
53
- notebook.remove_page(current_page)
54
- notebook.get_nth_page(notebook.page).grab_focus
55
- else
56
- notebook.remove_page(current_page)
57
- notebook.toplevel.application.quit
58
- end
52
+ notebook.remove_page(current_page)
53
+ notebook.toplevel.application.quit unless notebook.n_pages >= 1
59
54
  end
60
55
 
61
56
  signal_connect "window-title-changed" do |widget|
@@ -72,24 +67,20 @@ class TopinambourTerminal < Vte::Terminal
72
67
 
73
68
  builder = Gtk::Builder.new(:resource => "/com/github/cedlemo/topinambour/terminal-menu.ui")
74
69
  @menu = Gtk::Popover.new(self, builder["termmenu"])
75
-
70
+
76
71
  signal_connect "button-press-event" do |widget, event|
77
72
  if event.type == Gdk::EventType::BUTTON_PRESS &&
78
73
  event.button == Gdk::BUTTON_SECONDARY
79
-
80
- x, y = event.window.coords_to_parent(event.x,
81
- event.y)
82
- rect = Gdk::Rectangle.new(x - allocation.x,
83
- y - allocation.y,
84
- 1,
85
- 1)
86
- widget.menu.set_pointing_to(rect)
87
- widget.menu.show
74
+ display_copy_past_menu(widget, event)
88
75
  true
76
+ elsif event.button == Gdk::BUTTON_PRIMARY
77
+ manage_regex_on_click(widget, event)
78
+ false # let false so that it doesn't block the event
89
79
  else
90
80
  false
91
81
  end
92
82
  end
83
+
93
84
  configure
94
85
  end
95
86
 
@@ -97,20 +88,17 @@ class TopinambourTerminal < Vte::Terminal
97
88
  File.readlink("/proc/#{@pid}/cwd")
98
89
  end
99
90
 
100
- def get_css_colors
101
- background = nil
102
- foreground = nil
91
+ def css_colors
103
92
  colors = []
104
93
  background = parse_css_color(TERMINAL_COLOR_NAMES[0].to_s)
105
94
  foreground = parse_css_color(TERMINAL_COLOR_NAMES[1].to_s)
106
95
  TERMINAL_COLOR_NAMES[2..-1].each do |c|
107
- color = parse_css_color(c.to_s)
108
- colors << color
96
+ colors << parse_css_color(c.to_s)
109
97
  end
110
98
  [background, foreground] + colors
111
99
  end
112
100
 
113
- def get_css_font
101
+ def css_font
114
102
  font = style_get_property("font")
115
103
  unless font
116
104
  font = Pango::FontDescription.new(DEFAULT_TERMINAL_FONT)
@@ -134,15 +122,36 @@ class TopinambourTerminal < Vte::Terminal
134
122
  def configure
135
123
  set_rewrap_on_resize(true)
136
124
  set_scrollback_lines(-1)
137
- @colors = get_css_colors
138
- set_font(get_css_font)
125
+ @colors = css_colors
126
+ set_font(css_font)
139
127
  apply_colors
140
- #add_matches
128
+ add_matches
141
129
  end
142
130
 
143
131
  def add_matches
144
- puts methods.grep(/rege/)
145
- match_add_gregex("cedlemo")
132
+ @REGEXES = [:REGEX_URL_AS_IS, :REGEX_URL_FILE, :REGEX_URL_HTTP,
133
+ :REGEX_URL_VOIP, :REGEX_EMAIL, :REGEX_NEWS_MAN]
134
+ @REGEXES.each do |name|
135
+ regex_name = TopinambourRegex.const_get(name)
136
+ flags = [GLib::RegexCompileFlags::OPTIMIZE,
137
+ GLib::RegexCompileFlags::MULTILINE]
138
+ regex = GLib::Regex.new(regex_name, :compile_options => flags)
139
+ match_add_gregex(regex, 0)
140
+ end
141
+ end
142
+
143
+ def display_copy_past_menu(widget, event)
144
+ x, y = event.window.coords_to_parent(event.x,
145
+ event.y)
146
+ rect = Gdk::Rectangle.new(x - allocation.x,
147
+ y - allocation.y,
148
+ 1,
149
+ 1)
150
+ widget.menu.set_pointing_to(rect)
151
+ widget.menu.show
152
+ end
153
+
154
+ def manage_regex_on_click(widget, event)
155
+ #puts match_check_event(event).inspect
146
156
  end
147
157
  end
148
-
@@ -0,0 +1,100 @@
1
+ # Copyright 2016 Cédric LE MOIGNE, cedlemo@gmx.com
2
+ # This file is part of Topinambour.
3
+ #
4
+ # Topinambour is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # any later version.
8
+ #
9
+ # Topinambour is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Topinambour. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ # Stolen from https://github.com/GNOME/gnome-terminal/blob/master/src/terminal-regex.h
18
+ module TopinambourRegex
19
+ SCHEME = "(?ix: news | telnet | nntp | https? | ftps? | sftp | webcal )"
20
+ USERCHARS = "-+.[:alnum:]"
21
+ # Nonempty username, e.g. "john.smith"
22
+ USER = "[#{USERCHARS}]+"
23
+ PASSCHARS_CLASS = '[-[:alnum:]\\Q,?;.:/!%$^*&~\"#\'\\E]'
24
+ # Optional colon-prefixed password. I guess empty password should be allowed, right? E.g. ":secret", ":", "" */
25
+ PASS = "(?x: :#{PASSCHARS_CLASS}* )?"
26
+ # Optional at-terminated username (with perhaps a password too), e.g. "joe@", "pete:secret@", "" */
27
+ USERPASS = "(?:#{USER}#{PASS}@)?"
28
+ # S4: IPv4 segment (number between 0 and 255) with lookahead at the end so that we don't match "25" in the string "256".
29
+ # The lookahead could go to the last segment of IPv4 only but this construct allows nicer unittesting. */
30
+ S4_DEF = "(?(DEFINE)(?<S4>(?x: (?: [0-9] | [1-9][0-9] | 1[0-9]{2} | 2[0-4][0-9] | 25[0-5] ) (?! [0-9] ) )))"
31
+ # IPV4: Decimal IPv4, e.g. "1.2.3.4", with lookahead (implemented in S4) at the end so that we don't match "192.168.1.123" in the string "192.168.1.1234". */
32
+ IPV4_DEF = "#{S4_DEF}(?(DEFINE)(?<IPV4>(?x: (?: (?&S4) \\. ){3} (?&S4) )))"
33
+ # IPv6, including embedded IPv4, e.g. "::1", "dead:beef::1.2.3.4".
34
+ # Lookahead for the next char not being a dot or digit, so it doesn't get stuck matching "dead:beef::1" in "dead:beef::1.2.3.4".
35
+ # This is not required since the surrounding brackets would trigger backtracking, but it allows nicer unittesting.
36
+ # TODO: more strict check (right number of colons, etc.)
37
+ # TODO: add zone_id: RFC 4007 section 11, RFC 6874 */
38
+ # S6: IPv6 segment, S6C: IPv6 segment followed by a comma, CS6: comma followed by an IPv6 segment */
39
+ S6_DEF = "(?(DEFINE)(?<S6>[[:xdigit:]]{1,4})(?<CS6>:(?&S6))(?<S6C>(?&S6):))"
40
+ # No :: shorthand */
41
+ IPV6_FULL = "(?x: (?&S6C){7} (?&S6) )"
42
+ # Begins with :: */
43
+ IPV6_LEFT = "(?x: : (?&CS6){1,7} )"
44
+ # :: somewhere in the middle - use negative lookahead to make sure there aren't too many colons in total */
45
+ IPV6_MID = "(?x: (?! (?: [[:xdigit:]]*: ){8} ) (?&S6C){1,6} (?&CS6){1,6} )"
46
+ # Ends with :: */
47
+ IPV6_RIGHT = "(?x: (?&S6C){1,7} : )"
48
+ # Is "::" and nothing more */
49
+ IPV6_NULL = "(?x: :: )"
50
+ # The same ones for IPv4-embedded notation, without the actual IPv4 part */
51
+ IPV6V4_FULL = "(?x: (?&S6C){6} )"
52
+ IPV6V4_LEFT = "(?x: :: (?&S6C){0,5} )" # includes "::<ipv4>" */
53
+ IPV6V4_MID = "(?x: (?! (?: [[:xdigit:]]*: ){7} ) (?&S6C){1,4} (?&CS6){1,4} ) :"
54
+ IPV6V4_RIGHT= "(?x: (?&S6C){1,5} : )"
55
+ # IPV6: An IPv6 address (possibly with an embedded IPv4).
56
+ # This macro defines both IPV4 and IPV6, since the latter one requires the former. */
57
+ IP_DEF = "#{IPV4_DEF}#{S6_DEF}(?(DEFINE)(?<IPV6>(?x: (?: #{IPV6_NULL} | #{IPV6_LEFT} | #{IPV6_MID} | #{IPV6_RIGHT}" +
58
+ " | #{IPV6_FULL} | (?: #{IPV6V4_FULL} | #{IPV6V4_LEFT} | #{IPV6V4_MID} | #{IPV6V4_RIGHT}" +
59
+ " ) (?&IPV4) ) (?! [.:[:xdigit:]] ) )))"
60
+ # Either an alphanumeric character or dash; or if [negative lookahead] not ASCII
61
+ # then any graphical Unicode character.
62
+ # A segment can consist entirely of numbers.
63
+ # (Note: PCRE doesn't support character class subtraction/intersection.) */
64
+ HOSTNAMESEGMENTCHARS_CLASS = "(?x: [-[:alnum:]] | (?! [[:ascii:]] ) [[:graph:]] )"
65
+ # A hostname of at least 1 component. The last component cannot be entirely numbers.
66
+ # E.g. "foo", "example.com", "1234.com", but not "foo.123" */
67
+ HOSTNAME1 = "(?x: (?: #{HOSTNAMESEGMENTCHARS_CLASS}+ \\. )* " + "#{HOSTNAMESEGMENTCHARS_CLASS}* (?! [0-9] ) #{HOSTNAMESEGMENTCHARS_CLASS}+ )"
68
+ # A hostname of at least 2 components. The last component cannot be entirely numbers.
69
+ # E.g. "example.com", "1234.com", but not "1234.56" */
70
+ HOSTNAME2 = "(?x: (?: #{HOSTNAMESEGMENTCHARS_CLASS}+ \\.)+ #{HOSTNAME1} )"
71
+ # For URL: Hostname, IPv4, or bracket-enclosed IPv6, e.g. "example.com", "1.2.3.4", "[::1]" */
72
+ URL_HOST = "(?x: #{HOSTNAME1} | (?&IPV4) | \\[ (?&IPV6) \\] )"
73
+ # For e-mail: Hostname of at least two segments, or bracket-enclosed IPv4 or IPv6, e.g. "example.com", "[1.2.3.4]", "[::1]".
74
+ # Technically an e-mail with a single-component hostname might be valid on a local network,
75
+ # but let's avoid tons of false positives (e.g. in a typical shell prompt). */
76
+ EMAIL_HOST = "(?x: #{HOSTNAME2} | \\[ (?: (?&IPV4) | (?&IPV6) ) \\] )"
77
+ # Number between 1 and 65535, with lookahead at the end so that we don't match "6789" in the string "67890",
78
+ # and in turn we don't eventually match "http://host:6789" in "http://host:67890". */
79
+ N_1_65535 = "(?x: (?: [1-9][0-9]{0,3} | [1-5][0-9]{4} | 6[0-4][0-9]{3} | 65[0-4][0-9]{2} | 655[0-2][0-9] | 6553[0-5] ) (?! [0-9] ) )"
80
+ # Optional colon-prefixed port, e.g. ":1080", "" */
81
+ PORT = "(?x: \\:#{N_1_65535} )?"
82
+ PATHCHARS_CLASS = "[-[:alnum:]\\Q_$.+!*,:;@&=?/~#|%\\E]"
83
+ # Chars not to end a URL */
84
+ PATHNONTERM_CLASS = "[\\Q.!,?\\E]"
85
+ # Lookbehind at the end, so that the last character (if we matched a character at all) is not from PATHTERM_CLASS */
86
+ URLPATH = "(?x: /#{PATHCHARS_CLASS}* (?<! #{PATHNONTERM_CLASS} ) )?"
87
+ VOIP_PATH = "(?x: [;?]#{PATHCHARS_CLASS}* (?<! #{PATHNONTERM_CLASS} ) )?"
88
+ # Now let's put these fragments together */
89
+ DEFS = IP_DEF
90
+ #REGEX_URL_AS_IS = "#{SCHEME}://#{USERPASS}#{URL_HOST}#{PORT}#{URLPATH}"
91
+ REGEX_URL_AS_IS = "#{DEFS}#{SCHEME}#://#{USERPASS}#{URL_HOST}#{PORT}#{URLPATH}"
92
+ # TODO: also support file:/etc/passwd */
93
+ REGEX_URL_FILE = "#{DEFS}(?ix: file:/ (?: / (?: #{HOSTNAME1} )? / )? (?! / ) )(?x: #{PATHCHARS_CLASS}+ (?<! #{PATHNONTERM_CLASS} ) )?"
94
+ # Lookbehind so that we don't catch "abc.www.foo.bar", bug 739757.
95
+ # Lookahead for www/ftp for convenience (so that we can reuse HOSTNAME1). */
96
+ REGEX_URL_HTTP = "#{DEFS}(?<!(?:#{HOSTNAMESEGMENTCHARS_CLASS}|[.]))(?=(?i:www|ftp))#{HOSTNAME1}#{PORT}#{URLPATH}"
97
+ REGEX_URL_VOIP = "#{DEFS}(?i:h323:|sips?:)#{USERPASS}#{URL_HOST}#{PORT}#{VOIP_PATH}"
98
+ REGEX_EMAIL = "#{DEFS}(?i:mailto:)?#{USER}@#{EMAIL_HOST}"
99
+ REGEX_NEWS_MAN = "(?i:news:|man:|info:)[-[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+"
100
+ end
data/lib/window.rb CHANGED
@@ -63,6 +63,7 @@ class TopinambourWindow < Gtk::ApplicationWindow
63
63
  @notebook.append_page(terminal, Gtk::Label.new)
64
64
  @notebook.set_tab_reorderable(terminal, true)
65
65
  @notebook.set_page(@notebook.n_pages - 1)
66
+ @notebook.current.grab_focus
66
67
  end
67
68
 
68
69
  def quit_gracefully
@@ -78,11 +79,13 @@ class TopinambourWindow < Gtk::ApplicationWindow
78
79
  def show_prev_tab
79
80
  exit_overlay_mode
80
81
  @notebook.cycle_prev_page
82
+ @notebook.current.grab_focus
81
83
  end
82
84
 
83
85
  def show_next_tab
84
86
  exit_overlay_mode
85
87
  @notebook.cycle_next_page
88
+ @notebook.current.grab_focus
86
89
  end
87
90
 
88
91
  def show_terminal_chooser
@@ -191,6 +194,7 @@ class TopinambourWindow < Gtk::ApplicationWindow
191
194
  def toggle_overlay(klass)
192
195
  if in_overlay_mode? && @overlay.children[1].class == klass
193
196
  exit_overlay_mode
197
+ @notebook.current.grab_focus
194
198
  else
195
199
  exit_overlay_mode
196
200
  add_overlay(klass.new(self))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: topinambour
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cédric LE MOIGNE
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-14 00:00:00.000000000 Z
11
+ date: 2016-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vte3
@@ -113,6 +113,7 @@ files:
113
113
  - lib/style_properties.rb
114
114
  - lib/terminal.rb
115
115
  - lib/terminal_chooser.rb
116
+ - lib/terminal_regex.rb
116
117
  - lib/window.rb
117
118
  homepage: https://github.com/cedlemo/topinambour
118
119
  licenses: