[bug 706] 706-Favorites-Bookmarks-Plugin #706

- modified custom command plugin to have bookmarks
- added keybindings for directly adding or launch the custom command menu
- made name parsing of the custom command optional
This commit is contained in:
Vishweshwar Saran Singh Deo 2023-10-08 12:40:19 +05:30
parent 6d225c7aee
commit 86337ad326

View File

@ -1,8 +1,15 @@
# Terminator by Chris Jones <cmsj@tenshu.net> # Terminator by Chris Jones <cmsj@tenshu.net>
# GPL v2 only # GPL v2 only
#
# -added keybinding, bookmark functionality
# -made name parsing to menu, optional
# - Vishweshwar Saran Singh Deo vssdeo@gmail.com
# TODO: tags
"""custom_commands.py - Terminator Plugin to add custom command menu entries""" """custom_commands.py - Terminator Plugin to add custom command menu entries"""
import sys import sys
import os import os
import time
# Fix imports when testing this file directly # Fix imports when testing this file directly
if __name__ == '__main__': if __name__ == '__main__':
@ -14,8 +21,17 @@ import terminatorlib.plugin as plugin
from terminatorlib.config import Config from terminatorlib.config import Config
from terminatorlib.translation import _ from terminatorlib.translation import _
from terminatorlib.util import get_config_dir, err, dbg, gerr from terminatorlib.util import get_config_dir, err, dbg, gerr
from terminatorlib.terminator import Terminator
(CC_COL_ENABLED, CC_COL_NAME, CC_COL_COMMAND) = list(range(0,3)) from terminatorlib.plugin import KeyBindUtil
(CC_COL_ENABLED, CC_COL_NAME, CC_COL_NAME_PARSE, CC_COL_COMMAND) = list(range(0,4))
PluginActAdd = "plugin_add"
PluginActBmk = "plugin_bmk"
PluginAddDesc = "Plugin Add Bookmark"
PluginBmkDesc = "Plugin Open Bookmark Preferences"
# Every plugin you want Terminator to load *must* be listed in 'AVAILABLE' # Every plugin you want Terminator to load *must* be listed in 'AVAILABLE'
AVAILABLE = ['CustomCommandsMenu'] AVAILABLE = ['CustomCommandsMenu']
@ -25,10 +41,27 @@ class CustomCommandsMenu(plugin.MenuItem):
capabilities = ['terminal_menu'] capabilities = ['terminal_menu']
cmd_list = {} cmd_list = {}
conf_file = os.path.join(get_config_dir(),"custom_commands") conf_file = os.path.join(get_config_dir(),"custom_commands")
keyb = None
def __init__( self): def __init__( self):
# In prev code dbox is needed if _create_command_dialog func is called
# after configure func where dbox is init. In case we call
# _create_command_dialog without calling configure func, like in quick
# bookmark add then we need to check.
self.dbox = None
config = Config() config = Config()
sections = config.plugin_get_config(self.__class__.__name__) sections = config.plugin_get_config(self.__class__.__name__)
self.connect_signals()
self.keyb = KeyBindUtil(config)
self.keyb.bindkey_check_config(
[PluginAddDesc , PluginActAdd, "<Alt>b"])
self.keyb.bindkey_check_config(
[PluginBmkDesc , PluginActBmk, "<Shift><Alt>b"])
if not isinstance(sections, dict): if not isinstance(sections, dict):
return return
noord_cmds = [] noord_cmds = []
@ -38,23 +71,108 @@ class CustomCommandsMenu(plugin.MenuItem):
print("CustomCommandsMenu: Ignoring section %s" % s) print("CustomCommandsMenu: Ignoring section %s" % s)
continue continue
name = s["name"] name = s["name"]
name_parse = s.get("name_parse", "True")
command = s["command"] command = s["command"]
enabled = s["enabled"] and s["enabled"] or False enabled = s["enabled"] and s["enabled"] or False
if "position" in s: if "position" in s:
self.cmd_list[int(s["position"])] = {'enabled' : enabled, self.cmd_list[int(s["position"])] = {'enabled' : enabled,
'name' : name, 'name' : name,
'name_parse' : name_parse,
'command' : command 'command' : command
} }
else: else:
noord_cmds.append( noord_cmds.append(
{'enabled' : enabled, {'enabled' : enabled,
'name' : name, 'name' : name,
'name_parse' : name_parse,
'command' : command 'command' : command
} }
) )
for cmd in noord_cmds: for cmd in noord_cmds:
self.cmd_list[len(self.cmd_list)] = cmd self.cmd_list[len(self.cmd_list)] = cmd
def unload(self):
dbg("unloading")
for window in self.windows:
try:
window.disconnect_by_func(self.on_keypress)
except:
dbg("no connected signals")
self.keyb.unbindkey(
[PluginAddDesc , PluginActAdd, "<Alt>b"])
self.keyb.unbindkey(
[PluginBmkDesc , PluginActBmk, "<Shift><Alt>b"])
def connect_signals(self):
self.windows = Terminator().get_windows()
for window in self.windows:
window.connect('key-press-event', self.on_keypress)
def get_last_exe_cmd(self):
cur_win = Terminator().last_focused_term.get_toplevel()
#TODO: there has to be a better way to get the last command executed
focus_term = cur_win.get_focussed_terminal()
tmp_file = os.path.join(os.sep, 'tmp', 'term_cmd')
command = 'fc -n -l -1 -1 > ' + tmp_file + '; #bookmark last cmd\n'
focus_term.vte.feed_child(str(command).encode("utf-8"))
fsz = 0
count = 0
while not (count == 2 or fsz):
time.sleep(0.1)
if os.path.exists(tmp_file):
fsz = os.path.getsize(tmp_file)
count += 1
last_cmd = None
try:
with open(tmp_file, 'r') as file:
last_cmd = file.read()
file.close()
except Exception as ex:
err('Unable to open %s ex: (%s)' % (tmp_file, ex))
if os.path.exists(tmp_file):
os.remove(tmp_file)
if last_cmd:
last_cmd = last_cmd.rstrip()
dbg('last exec cmd: (%s)' % last_cmd)
return last_cmd
def get_last_exe_cmd_dialog_vars(self):
last_exe_cmd = self.get_last_exe_cmd()
dialog_vars = { 'enabled' : True,
'name' : last_exe_cmd,
'name_parse': False,
'command' : last_exe_cmd }
return dialog_vars
def on_keypress(self, widget, event):
act = self.keyb.keyaction(event)
dbg("keyaction: (%s) (%s)" % (str(act), event.keyval))
if act == PluginActAdd:
dbg("add bookmark")
dialog_vars = self.get_last_exe_cmd_dialog_vars()
self.on_new(None, {'dialog_vars' : dialog_vars })
self.update_cmd_list(self.store)
self._save_config()
return True
if act == PluginActBmk:
dbg("open bookmark preferences")
cur_win = Terminator().last_focused_term.get_toplevel()
self.configure(cur_win)
return True
def callback(self, menuitems, menu, terminal): def callback(self, menuitems, menu, terminal):
"""Add our menu items to the menu""" """Add our menu items to the menu"""
submenus = {} submenus = {}
@ -81,16 +199,20 @@ class CustomCommandsMenu(plugin.MenuItem):
branch_names = command['name'].split('/')[:-1] branch_names = command['name'].split('/')[:-1]
target_submenu = submenu target_submenu = submenu
parent_submenu = submenu parent_submenu = submenu
for idx in range(len(branch_names)): if not command['name_parse']:
lookup_name = '/'.join(branch_names[0:idx+1]) leaf_name = command['name']
target_submenu = submenus.get(lookup_name, None) branch_names = ''
if not target_submenu: else:
item = Gtk.MenuItem(_(branch_names[idx])) for idx in range(len(branch_names)):
parent_submenu.append(item) lookup_name = '/'.join(branch_names[0:idx+1])
target_submenu = Gtk.Menu() target_submenu = submenus.get(lookup_name, None)
item.set_submenu(target_submenu) if not target_submenu:
submenus[lookup_name] = target_submenu item = Gtk.MenuItem(_(branch_names[idx]))
parent_submenu = target_submenu parent_submenu.append(item)
target_submenu = Gtk.Menu()
item.set_submenu(target_submenu)
submenus[lookup_name] = target_submenu
parent_submenu = target_submenu
if iconinfo: if iconinfo:
image = Gtk.Image() image = Gtk.Image()
image.set_from_icon_name(exe, Gtk.IconSize.MENU) image.set_from_icon_name(exe, Gtk.IconSize.MENU)
@ -109,11 +231,13 @@ class CustomCommandsMenu(plugin.MenuItem):
for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ] : for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ] :
enabled = command['enabled'] enabled = command['enabled']
name = command['name'] name = command['name']
name_parse = command['name_parse']
command = command['command'] command = command['command']
item = {} item = {}
item['enabled'] = enabled item['enabled'] = enabled
item['name'] = name item['name'] = name
item['name_parse'] = name_parse
item['command'] = command item['command'] = command
item['position'] = i item['position'] = i
@ -128,6 +252,13 @@ class CustomCommandsMenu(plugin.MenuItem):
for terminal in data['terminals']: for terminal in data['terminals']:
terminal.vte.feed_child(command.encode()) terminal.vte.feed_child(command.encode())
def setup_store(self):
self.store = Gtk.ListStore(bool, str, bool, str)
for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ]:
self.store.append([command['enabled'], command['name'],
command['name_parse'], command['command']])
return self.store
def configure(self, widget, data = None): def configure(self, widget, data = None):
ui = {} ui = {}
dbox = Gtk.Dialog( dbox = Gtk.Dialog(
@ -149,11 +280,8 @@ class CustomCommandsMenu(plugin.MenuItem):
icon = dbox.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON) icon = dbox.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON)
dbox.set_icon(icon) dbox.set_icon(icon)
store = Gtk.ListStore(bool, str, str) store = self.setup_store()
for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ]:
store.append([command['enabled'], command['name'], command['command']])
treeview = Gtk.TreeView(store) treeview = Gtk.TreeView(store)
#treeview.connect("cursor-changed", self.on_cursor_changed, ui) #treeview.connect("cursor-changed", self.on_cursor_changed, ui)
selection = treeview.get_selection() selection = treeview.get_selection()
@ -226,7 +354,11 @@ class CustomCommandsMenu(plugin.MenuItem):
button.set_sensitive(False) button.set_sensitive(False)
ui['button_delete'] = button ui['button_delete'] = button
button = Gtk.Button(_("Bookmark Last Cmd"))
button_box.pack_start(button, False, True, 0)
ui['dialog_vars'] = self.get_last_exe_cmd_dialog_vars()
button.connect("clicked", self.on_new, ui)
ui['button_save_last_cmd'] = button
hbox.pack_start(button_box, False, True, 0) hbox.pack_start(button_box, False, True, 0)
self.dbox = dbox self.dbox = dbox
@ -237,6 +369,7 @@ class CustomCommandsMenu(plugin.MenuItem):
self._save_config() self._save_config()
del(self.dbox) del(self.dbox)
dbox.destroy() dbox.destroy()
self.dbox = None
return return
@ -245,12 +378,14 @@ class CustomCommandsMenu(plugin.MenuItem):
self.cmd_list = {} self.cmd_list = {}
i=0 i=0
while iter: while iter:
(enabled, name, command) = store.get(iter, (enabled, name, name_parse, command) = store.get(iter,
CC_COL_ENABLED, CC_COL_ENABLED,
CC_COL_NAME, CC_COL_NAME,
CC_COL_NAME_PARSE,
CC_COL_COMMAND) CC_COL_COMMAND)
self.cmd_list[i] = {'enabled' : enabled, self.cmd_list[i] = {'enabled' : enabled,
'name': name, 'name': name,
'name_parse' : name_parse,
'command' : command} 'command' : command}
iter = store.iter_next(iter) iter = store.iter_next(iter)
i = i + 1 i = i + 1
@ -278,7 +413,8 @@ class CustomCommandsMenu(plugin.MenuItem):
data['button_edit'].set_sensitive(iter is not None) data['button_edit'].set_sensitive(iter is not None)
data['button_delete'].set_sensitive(iter is not None) data['button_delete'].set_sensitive(iter is not None)
def _create_command_dialog(self, enabled_var = False, name_var = "", command_var = ""): def _create_command_dialog(self, enabled_var = False, name_var = "",
name_parse_var = "", command_var = ""):
dialog = Gtk.Dialog( dialog = Gtk.Dialog(
_("New Command"), _("New Command"),
None, None,
@ -288,38 +424,72 @@ class CustomCommandsMenu(plugin.MenuItem):
_("_OK"), Gtk.ResponseType.ACCEPT _("_OK"), Gtk.ResponseType.ACCEPT
) )
) )
dialog.set_transient_for(self.dbox) # dbox is init in configure function, in case we want to
table = Gtk.Table(3, 2) # create dialog directly
if self.dbox:
dialog.set_transient_for(self.dbox)
table = Gtk.Table(4, 2)
table.set_row_spacings(5)
table.set_col_spacings(5)
label = Gtk.Label(label=_("Enabled:")) label = Gtk.Label(label=_("Enabled:"))
label.set_alignment(0, 0)
table.attach(label, 0, 1, 0, 1) table.attach(label, 0, 1, 0, 1)
enabled = Gtk.CheckButton() enabled = Gtk.CheckButton()
enabled.set_active(enabled_var) enabled.set_active(enabled_var)
table.attach(enabled, 1, 2, 0, 1) table.attach(enabled, 1, 2, 0, 1)
label = Gtk.Label(label=_("Name:")) label = Gtk.Label(label=_("Parse Name into SubMenu's:"))
label.set_alignment(0, 0)
table.attach(label, 0, 1, 1, 2) table.attach(label, 0, 1, 1, 2)
name_parse = Gtk.CheckButton()
name_parse.set_active(name_parse_var)
table.attach(name_parse, 1, 2, 1, 2)
label = Gtk.Label(label=_("Name:"))
table.attach(label, 0, 1, 2, 3)
name = Gtk.Entry() name = Gtk.Entry()
name.set_text(name_var) name.set_text(name_var)
table.attach(name, 1, 2, 1, 2) table.attach(name, 1, 2, 2, 3)
label = Gtk.Label(label=_("Command:")) label = Gtk.Label(label=_("Command:"))
table.attach(label, 0, 1, 2, 3) table.attach(label, 0, 1, 3, 4)
command = Gtk.TextView() command = Gtk.TextView()
command.get_buffer().set_text(command_var) command.get_buffer().set_text(command_var)
table.attach(command, 1, 2, 2, 3) table.attach(command, 1, 2, 3, 4)
dialog.vbox.pack_start(table, True, True, 0) dialog.vbox.pack_start(table, True, True, 10)
dialog.show_all() dialog.show_all()
return (dialog,enabled,name,command) return (dialog,enabled,name,name_parse,command)
def on_new(self, button, data): def on_new(self, button, data):
(dialog,enabled,name,command) = self._create_command_dialog()
#default values can be passed to dialogue window if required
enabled_var = ''
name_var = ''
name_parse_var= ''
command_var = ''
if data and 'dialog_vars' in data:
dialog_vars = data.get('dialog_vars', {})
enabled_var = dialog_vars.get('enabled', True)
name_var = dialog_vars.get('name', '')
name_parse_var= dialog_vars.get('name_parse', False)
command_var = dialog_vars.get('command', '')
(dialog,enabled,name,name_parse,command) = self._create_command_dialog(
enabled_var = enabled_var,
name_var = name_var,
name_parse_var = name_parse_var,
command_var = command_var)
res = dialog.run() res = dialog.run()
item = {} item = {}
if res == Gtk.ResponseType.ACCEPT: if res == Gtk.ResponseType.ACCEPT:
item['enabled'] = enabled.get_active() item['enabled'] = enabled.get_active()
item['name'] = name.get_text() item['name'] = name.get_text()
item['name_parse'] = name_parse.get_active()
item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True) item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True)
if item['name'] == '' or item['command'] == '': if item['name'] == '' or item['command'] == '':
err = Gtk.MessageDialog(dialog, err = Gtk.MessageDialog(dialog,
@ -332,7 +502,9 @@ class CustomCommandsMenu(plugin.MenuItem):
err.destroy() err.destroy()
else: else:
# we have a new command # we have a new command
store = data['treeview'].get_model() store = data['treeview'].get_model() if 'treeview' in data else None
if not store:
store = self.setup_store()
iter = store.get_iter_first() iter = store.get_iter_first()
name_exist = False name_exist = False
while iter != None: while iter != None:
@ -341,7 +513,8 @@ class CustomCommandsMenu(plugin.MenuItem):
break break
iter = store.iter_next(iter) iter = store.iter_next(iter)
if not name_exist: if not name_exist:
store.append((item['enabled'], item['name'], item['command'])) store.append((item['enabled'], item['name'],
item['name_parse'], item['command']))
else: else:
gerr(_("Name *%s* already exist") % item['name']) gerr(_("Name *%s* already exist") % item['name'])
dialog.destroy() dialog.destroy()
@ -420,16 +593,17 @@ class CustomCommandsMenu(plugin.MenuItem):
if not iter: if not iter:
return return
(dialog,enabled,name,command) = self._create_command_dialog( (dialog,enabled,name,name_parse,command) = self._create_command_dialog(
enabled_var = store.get_value(iter, CC_COL_ENABLED), enabled_var = store.get_value(iter, CC_COL_ENABLED),
name_var = store.get_value(iter, CC_COL_NAME), name_var = store.get_value(iter, CC_COL_NAME),
command_var = store.get_value(iter, CC_COL_COMMAND) name_parse_var = store.get_value(iter, CC_COL_NAME_PARSE),
) command_var = store.get_value(iter, CC_COL_COMMAND))
res = dialog.run() res = dialog.run()
item = {} item = {}
if res == Gtk.ResponseType.ACCEPT: if res == Gtk.ResponseType.ACCEPT:
item['enabled'] = enabled.get_active() item['enabled'] = enabled.get_active()
item['name'] = name.get_text() item['name'] = name.get_text()
item['name_parse'] = name_parse.get_active()
item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True) item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True)
if item['name'] == '' or item['command'] == '': if item['name'] == '' or item['command'] == '':
err = Gtk.MessageDialog(dialog, err = Gtk.MessageDialog(dialog,
@ -452,14 +626,15 @@ class CustomCommandsMenu(plugin.MenuItem):
store.set(iter, store.set(iter,
CC_COL_ENABLED,item['enabled'], CC_COL_ENABLED,item['enabled'],
CC_COL_NAME, item['name'], CC_COL_NAME, item['name'],
CC_COL_NAME_PARSE, item['name_parse'],
CC_COL_COMMAND, item['command'] CC_COL_COMMAND, item['command']
) )
else: else:
gerr(_("Name *%s* already exist") % item['name']) gerr(_("Name *%s* already exist") % item['name'])
dialog.destroy() dialog.destroy()
if __name__ == '__main__': if __name__ == '__main__':
c = CustomCommandsMenu() c = CustomCommandsMenu()
c.configure(None, None) c.configure(None, None)