Merge lp:~dcaro/clicompanion/fix-923535 into lp:clicompanion

Proposed by David Caro
Status: Merged
Approved by: Marek Bardoński
Approved revision: 112
Merged at revision: 108
Proposed branch: lp:~dcaro/clicompanion/fix-923535
Merge into: lp:clicompanion
Prerequisite: lp:~dcaro/clicompanion/fix-623475
Diff against target: 382 lines (+247/-10)
7 files modified
MANIFEST (+2/-0)
clicompanionlib/config.py (+1/-1)
clicompanionlib/plugins.py (+41/-1)
clicompanionlib/tabs.py (+48/-7)
clicompanionlib/view.py (+2/-1)
plugins/LaunchpadURL.py (+62/-0)
plugins/StandardURLs.py (+91/-0)
To merge this branch: bzr merge lp:~dcaro/clicompanion/fix-923535
Reviewer Review Type Date Requested Status
Marek Bardoński Approve
Review via email: mp+90633@code.launchpad.net

Description of the change

Added the urls plugins to allow the user to click on urls to open them, added the standard (http/https/ftp/ftps/file/news/sip/etc.) and launchpad (lp:bugcode or lp:branch) to the pluginslist.

To post a comment you must log in.
lp:~dcaro/clicompanion/fix-923535 updated
111. By David Caro "<email address hidden>"

added new url plugins

Revision history for this message
Marek Bardoński (bdfhjk) wrote :

Hi David!

Sorry for long time before review, I have much work with preparation for very important for me contest.

I have applied your path, but I can't see how it work. After typing http://www.google.com in terminal and click [enter], it isn't appear as link and isn't clickable.

Please explain, how your path should work - and thank you for working on it!

Marek

review: Needs Information
Revision history for this message
David Caro (dcaro) wrote :

You have to go to the plugins configuration and enable them first (that's because the config file is not overwrited if it exists).

So for this to work, just go to Preferences -> Plugins and double click the checkbox of the StandardURLs plugin and LaunchpadURL plugin. After this (inmediatly, you don't need to restart) when you pass the mouse over a recognized url, it will be underscored, in that moment you can press ctrl+click and that will open a tab/window of your preferred browser.

I hope you like it!

Revision history for this message
Marek Bardoński (bdfhjk) wrote :

OK, thanks for explain. I will try it now.

Revision history for this message
Marek Bardoński (bdfhjk) wrote :

I found small bugs:

After typing lp:clicompanion and pressing [enter], then created link is '
https://code.launchpad.net/+branch/clicompanion:%20nie%20znaleziono%20poleceniauser@user-GX780-GT780-GT780DX:~/clicompanion/clicompanion$%20%5EC'
created from underlined ' lp:clicompanion: nie znaleziono polecenia \n
user@user-GX780-GT780-GT780DX:~/clicompanion/clicompanion'.

After typing lp:12312 and pressing [enter], I have two links - one correct
to bug, and one '
https://code.launchpad.net/+branch/12122:%20nie%20znaleziono%20poleceniauser@user-GX780-GT780-GT780DX:~/clicompanion/clicompanion$%20%5EC
'

lp:~dcaro/clicompanion/fix-923535 updated
112. By David Caro "<email address hidden>"

The url matching regexp was way too permissive, and incorrect paths were
matched

Revision history for this message
David Caro (dcaro) wrote :

Fixed, the regexp matching launchpad branches was too permissive and false positives were matched. It should be fixed now (it's a lot more strict, so maybe real urls that have some strange chars in them are not matched correctly, but now most of urls will match).

Also you don't need to hit enter for the url to get underscored ;)

Revision history for this message
Marek Bardoński (bdfhjk) wrote :

2012/3/4 David Caro <email address hidden>

> Fixed, the regexp matching launchpad branches was too permissive and false
> positives were matched. It should be fixed now (it's a lot more strict, so
> maybe real urls that have some strange chars in them are not matched
> correctly, but now most of urls will match).
>
> Also you don't need to hit enter for the url to get underscored ;)
>
> --
> https://code.launchpad.net/~dcaro/clicompanion/fix-923535/+merge/90633<https://code.launchpad.net/%7Edcaro/clicompanion/fix-923535/+merge/90633>
> You are reviewing the proposed merge of lp:~dcaro/clicompanion/fix-923535
> into lp:clicompanion.
>

OK, I will test it soon.

Revision history for this message
Marek Bardoński (bdfhjk) wrote :

Tested - problems solved.

Thanks for implementing nice feature!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'MANIFEST'
--- MANIFEST 2012-01-08 01:37:38 +0000
+++ MANIFEST 2012-03-04 09:53:18 +0000
@@ -17,4 +17,6 @@
17plugins/CommandLineFU.py17plugins/CommandLineFU.py
18plugins/__init__.py18plugins/__init__.py
19plugins/LocalCommandList.py19plugins/LocalCommandList.py
20plugins/StandardURLs.py
21plugins/LaunchpadURL.py
2022
2123
=== modified file 'clicompanionlib/config.py'
--- clicompanionlib/config.py 2012-01-15 14:23:05 +0000
+++ clicompanionlib/config.py 2012-03-04 09:53:18 +0000
@@ -60,7 +60,7 @@
60 "update_login_records": ("True", 'bool'),60 "update_login_records": ("True", 'bool'),
61 },61 },
62 'general': {"debug": ('False', 'bool'),62 'general': {"debug": ('False', 'bool'),
63 "plugins": ('LocalCommandList, CommandLineFU', 'str')63 "plugins": ('LocalCommandList, CommandLineFU, StandardURLs', 'str')
64 },64 },
65 'LocalCommandList': {"cheatsheet":65 'LocalCommandList': {"cheatsheet":
66 (os.path.expanduser("~/.clicompanion2"), 'str'),66 (os.path.expanduser("~/.clicompanion2"), 'str'),
6767
=== modified file 'clicompanionlib/plugins.py'
--- clicompanionlib/plugins.py 2012-01-15 14:23:05 +0000
+++ clicompanionlib/plugins.py 2012-03-04 09:53:18 +0000
@@ -47,6 +47,7 @@
47import sys47import sys
48import os48import os
49import inspect49import inspect
50import webbrowser
50from clicompanionlib.utils import dbg51from clicompanionlib.utils import dbg
5152
5253
@@ -97,7 +98,7 @@
97 if capability in capabilities \98 if capability in capabilities \
98 and plugin in self.allowed:99 and plugin in self.allowed:
99 plugins.append((plugin, pclass))100 plugins.append((plugin, pclass))
100 dbg('Matching plugin %s for %s' % (plugin, capability))101 dbg('Matching plugin %s for capability %s' % (plugin, capability))
101 return plugins102 return plugins
102103
103 def get_plugin_conf(self, plugin):104 def get_plugin_conf(self, plugin):
@@ -207,3 +208,42 @@
207 def __init__(self, config):208 def __init__(self, config):
208 Plugin.__init__(self)209 Plugin.__init__(self)
209 self.config = config210 self.config = config
211
212class URLPlugin(Plugin):
213 '''
214 Plugion that matches an url in the screen and executes some action.
215 '''
216 __capabilities__ = [ 'URL' ]
217
218
219 def __init__(self, config):
220 Plugin.__init__(self)
221 self.config = config
222 ## This is the regexp that will trigger the callback
223 matches = ['']
224
225 def callback(self, url, matchnum):
226 ## When the regexp is found, this function will be called
227 pass
228
229 def open_url(self, url):
230 """
231 Open a given URL, generic for all the URL plugins to use
232 """
233 oldstyle = False
234 if gtk.gtk_version < (2, 14, 0) or \
235 not hasattr(gtk, 'show_uri') or \
236 not hasattr(gtk.gdk, 'CURRENT_TIME'):
237 oldstyle = True
238 if not oldstyle:
239 try:
240 gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
241 except:
242 oldstyle = True
243 if oldstyle:
244 dbg('Old gtk (%s,%s,%s), calling xdg-open' % gtk.gtk_version)
245 try:
246 subprocess.Popen(["xdg-open", url])
247 except:
248 dbg('xdg-open did not work, falling back to webbrowser.open')
249 webbrowser.open(url)
210250
=== modified file 'clicompanionlib/tabs.py'
--- clicompanionlib/tabs.py 2012-01-15 20:40:56 +0000
+++ clicompanionlib/tabs.py 2012-03-04 09:53:18 +0000
@@ -42,6 +42,7 @@
42import clicompanionlib.utils as cc_utils42import clicompanionlib.utils as cc_utils
43import clicompanionlib.helpers as cc_helpers43import clicompanionlib.helpers as cc_helpers
44import clicompanionlib.preferences as cc_pref44import clicompanionlib.preferences as cc_pref
45import clicompanionlib.config as cc_conf
4546
4647
47class TerminalTab(gtk.ScrolledWindow):48class TerminalTab(gtk.ScrolledWindow):
@@ -56,12 +57,15 @@
56 ()),57 ()),
57 }58 }
5859
59 def __init__(self, title, config, profile='default', directory=None):60 def __init__(self, title, config, profile='default', directory=None,
61 pluginloader=None):
60 gtk.ScrolledWindow.__init__(self)62 gtk.ScrolledWindow.__init__(self)
61 self.config = config63 self.config = config
64 self.pluginloader = pluginloader
62 self.title = title65 self.title = title
63 self.profile = 'profile::' + profile66 self.profile = 'profile::' + profile
64 self.vte = vte.Terminal()67 self.vte = vte.Terminal()
68 self.matches = {}
65 self.add(self.vte)69 self.add(self.vte)
66 self.vte.connect("child-exited", lambda *x: self.emit('quit'))70 self.vte.connect("child-exited", lambda *x: self.emit('quit'))
67 self.update_records = self.config.getboolean(self.profile,71 self.update_records = self.config.getboolean(self.profile,
@@ -78,7 +82,7 @@
78 logutmp=self.update_records,82 logutmp=self.update_records,
79 logwtmp=self.update_records,83 logwtmp=self.update_records,
80 loglastlog=self.update_records)84 loglastlog=self.update_records)
81 self.vte.connect("button_press_event", self.copy_paste_menu)85 self.vte.connect("button_press_event", self.on_click)
82 self.update_config()86 self.update_config()
83 self.show_all()87 self.show_all()
8488
@@ -205,8 +209,34 @@
205209
206 self.vte.set_allow_bold(config.getboolean(self.profile, 'bold_text'))210 self.vte.set_allow_bold(config.getboolean(self.profile, 'bold_text'))
207 self.vte.set_word_chars(config.get(self.profile, 'sel_word'))211 self.vte.set_word_chars(config.get(self.profile, 'sel_word'))
208212 self.vte.match_clear_all()
209 def copy_paste_menu(self, vte, event):213 self.load_url_plugins()
214
215 def check_for_match(self, event):
216 """
217 Check if the mouse is over a URL
218 """
219 return (self.vte.match_check(int(event.x / self.vte.get_char_width()),
220 int(event.y / self.vte.get_char_height())))
221
222 def run_match_callback(self, match):
223 url = match[0]
224 match = match[1]
225 for plg, m_plg in self.matches.items():
226 if match in m_plg[1]:
227 dbg('Matched %s for url %s' % (plg, url))
228 matchnum = m_plg[1].index(match)
229 m_plg[0].callback(url, matchnum)
230
231 def on_click(self, vte, event):
232 ## left click
233 if event.button == 1:
234 # Ctrl+leftclick on a URL should open it
235 if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK:
236 match = self.check_for_match(event)
237 if match:
238 self.run_match_callback(match)
239 ## Rght click menu
210 if event.button == 3:240 if event.button == 3:
211 time = event.time241 time = event.time
212 ## right-click popup menu Copy242 ## right-click popup menu Copy
@@ -366,6 +396,14 @@
366 self.profile = 'profile::' + profile396 self.profile = 'profile::' + profile
367 self.update_config()397 self.update_config()
368398
399 def load_url_plugins(self):
400 for pg_name, pg_class in self.pluginloader.get_plugins(['URL']):
401 pg_conf = cc_conf.CLIConfigView(pg_name, self.config)
402 self.matches[pg_name] = (pg_class(pg_conf), [])
403 for match in self.matches[pg_name][0].matches:
404 dbg('Adding match %s for plugin %s' % (match, pg_name))
405 self.matches[pg_name][1].append(self.vte.match_add(match))
406
369407
370class TerminalsNotebook(gtk.Notebook):408class TerminalsNotebook(gtk.Notebook):
371 __gsignals__ = {409 __gsignals__ = {
@@ -375,11 +413,12 @@
375 ()),413 ()),
376 }414 }
377415
378 def __init__(self, config):416 def __init__(self, config, pluginloader):
379 gtk.Notebook.__init__(self)417 gtk.Notebook.__init__(self)
380 #definition gcp - global page count, how many pages have been created418 #definition gcp - global page count, how many pages have been created
381 self.gcp = 0419 self.gcp = 0
382 self.global_config = config420 self.global_config = config
421 self.pluginloader = pluginloader
383 ## The "Add Tab" tab422 ## The "Add Tab" tab
384 add_tab_button = gtk.Button("+")423 add_tab_button = gtk.Button("+")
385 ## tooltip for "Add Tab" tab424 ## tooltip for "Add Tab" tab
@@ -428,9 +467,11 @@
428 current_page = self.get_nth_page(self.get_current_page())467 current_page = self.get_nth_page(self.get_current_page())
429 cwd = cc_utils.get_pid_cwd(current_page.pid)468 cwd = cc_utils.get_pid_cwd(current_page.pid)
430 if cwd:469 if cwd:
431 newtab = TerminalTab(title, self.global_config, directory=cwd)470 newtab = TerminalTab(title, self.global_config, directory=cwd,
471 pluginloader=self.pluginloader)
432 else:472 else:
433 newtab = TerminalTab(title, self.global_config)473 newtab = TerminalTab(title, self.global_config,
474 pluginloader=self.pluginloader)
434 label = self.create_tab_label(title, newtab)475 label = self.create_tab_label(title, newtab)
435 self.insert_page(newtab, label, self.get_n_pages() - 1)476 self.insert_page(newtab, label, self.get_n_pages() - 1)
436 self.set_current_page(self.get_n_pages() - 2)477 self.set_current_page(self.get_n_pages() - 2)
437478
=== modified file 'clicompanionlib/view.py'
--- clicompanionlib/view.py 2012-01-28 19:38:19 +0000
+++ clicompanionlib/view.py 2012-03-04 09:53:18 +0000
@@ -223,7 +223,8 @@
223223
224 ## set various parameters on the main window (size, etc)224 ## set various parameters on the main window (size, etc)
225 self.init_config()225 self.init_config()
226 self.term_notebook = cc_tabs.TerminalsNotebook(self.config)226 self.term_notebook = cc_tabs.TerminalsNotebook(self.config,
227 self.pluginloader)
227228
228 ###########################229 ###########################
229 #### Here we create the commands notebook for the expander230 #### Here we create the commands notebook for the expander
230231
=== added file 'plugins/LaunchpadURL.py'
--- plugins/LaunchpadURL.py 1970-01-01 00:00:00 +0000
+++ plugins/LaunchpadURL.py 2012-03-04 09:53:18 +0000
@@ -0,0 +1,62 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# LaunchpadURL.py - URL plugin for launchpad bugs, repos and code
5#
6# Copyright 2012 David Caro <david.caro.estevez@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21
22import pygtk
23pygtk.require('2.0')
24import gobject
25import webbrowser
26
27try:
28 import gtk
29except:
30 ## do not use gtk, just print
31 print _("You need to install the python gtk bindings package"
32 "'python-gtk2'")
33 sys.exit(1)
34
35from clicompanionlib.utils import dbg
36import clicompanionlib.plugins as plugins
37
38
39class LaunchpadURL(plugins.URLPlugin):
40 '''
41 Match launchpad urls and open them on the browser
42 '''
43 __authors__ = 'David Caro <david.caro.estevez@gmail.com>'
44 __info__ = ('This plugins enables launchpad urls to be matched.')
45 __title__ = 'Launchpad URLS'
46
47 pathchars = "-A-Za-z0-9_.+?/~#"
48 nonums = "-A-Za-z_.+?/~#"
49
50 def __init__(self, config):
51 plugins.URLPlugin.__init__(self, config)
52 self.matches = ['lp:[0-9]+',
53 'lp:[' + self.pathchars + ']*[' \
54 + self.nonums + '][' + self.pathchars + ']*']
55
56 def callback(self, url, matchnum):
57 dbg('Openeing launchpad url ' + url)
58 if matchnum == 0:
59 url = 'http://bugs.launchpad.net/bugs/' + url[3:]
60 else:
61 url = 'http://code.launchpad.net/+branch/' + url[3:]
62 self.open_url(url)
063
=== added file 'plugins/StandardURLs.py'
--- plugins/StandardURLs.py 1970-01-01 00:00:00 +0000
+++ plugins/StandardURLs.py 2012-03-04 09:53:18 +0000
@@ -0,0 +1,91 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# StandardURLs.py - URL plugin for common urls (http, ftp, etc.)
5#
6# Copyright 2012 David Caro <david.caro.estevez@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21
22import pygtk
23pygtk.require('2.0')
24import gobject
25import webbrowser
26
27try:
28 import gtk
29except:
30 ## do not use gtk, just print
31 print _("You need to install the python gtk bindings package"
32 "'python-gtk2'")
33 sys.exit(1)
34
35from clicompanionlib.utils import dbg
36import clicompanionlib.plugins as plugins
37
38
39class StandardURLs(plugins.URLPlugin):
40 '''
41 Match launchpad urls and open them on the browser
42 '''
43 __authors__ = 'David Caro <david.caro.estevez@gmail.com>'
44 __info__ = ('This plugins enables some common urls to be matched.')
45 __title__ = 'Standard URLS'
46
47 def __init__(self, config):
48 plugins.URLPlugin.__init__(self, config)
49 self.matches = []
50
51 userchars = "-A-Za-z0-9"
52 passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
53 hostchars = "-A-Za-z0-9"
54 pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'\""
55 schemes = ("(news:|telnet:|nntp:|https?:|ftps?:|webcal:)//")
56 user = "([" + userchars + "]+(:[" + passchars + "]+)?)?"
57 urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
58 email = ("[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*"
59 "\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*")
60
61 lboundry = "\\<"
62 rboundry = "\\>"
63
64 ## http/https/ftp/ftps/webcal/nntp/telnet urls
65 self.matches.append(schemes + user + "[" + hostchars + "]*\.["
66 + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?")
67 ## file
68 self.matches.append('file:///[' + pathchars + "]*")
69 ## SIP
70 self.matches.append('(callto:|h323:|sip:)'
71 + "[" + userchars + "+]["
72 + userchars + ".]*(:[0-9]+)?@?["
73 + pathchars + "]+"
74 + rboundry)
75 ## mail
76 self.matches.append("(mailto:)?" + email)
77 ## news
78 self.matches.append('news:[-A-Z\^_a-z{|}~!"#$%&\'()*+'
79 + ',./0-9;:=?`]+@' + "[-A-Za-z0-9.]+(:[0-9]+)?")
80 ## General url (www.host.com or ftp.host.com)
81 self.matches.append("(www|ftp)[" + hostchars + "]*\.["
82 + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?/?")
83
84 def callback(self, url, matchnum):
85 dbg('Opening common url ' + url)
86 if matchnum == 5:
87 if url[:3] == 'www':
88 url = 'http://' + url
89 else:
90 url = 'ftp://' + url
91 self.open_url(url)

Subscribers

People subscribed via source and target branches