Over the weekend, I picked up a project via Reddit involving a plugin for the Flow Launcher. I created a fzf and rofi version for my Ubuntu Linux environment, and then thought, how hard can it be to port it to uLauncher?
Here I document what I did.
1. Inside ~/.local/share/ulauncher/extensions/
create a new dir. In my case, I created ~/.local/share/ulauncher/extensions/com.github.ubuntupunk.ulauncher-vim
2. Touch the following files:
├── images
│ └── icon.png
├── versions.json
├── manifest.json
└── main.py
Enter fullscreen mode Exit fullscreen mode
3. In versions.json
place the following boilerplate:
[ {"required_api_version": "2", "commit": "master"} ]
Enter fullscreen mode Exit fullscreen mode
4. In manifest.json
{ "required_api_version": "2", "name": "Demo extension", "description": "Extension Description", "developer_name": "John Doe", "icon": "images/icon.png", "options": { "query_debounce": 0.1 }, "preferences": [ { "id": "demo_kw", "type": "keyword", "name": "Demo", "description": "Demo extension", "default_value": "dm" } ] }
Enter fullscreen mode Exit fullscreen mode
5. In main.py
from ulauncher.api.client.Extension import Extension
from ulauncher.api.client.EventListener import EventListener
from ulauncher.api.shared.event import KeywordQueryEvent, ItemEnterEvent
from ulauncher.api.shared.item.ExtensionResultItem import ExtensionResultItem
from ulauncher.api.shared.action.RenderResultListAction import RenderResultListAction
from ulauncher.api.shared.action.HideWindowAction import HideWindowAction
class DemoExtension(Extension):
def __init__(self):
super().__init__()
self.subscribe(KeywordQueryEvent, KeywordQueryEventListener())
class KeywordQueryEventListener(EventListener):
def on_event(self, event, extension):
items = []
for i in range(5):
items.append(ExtensionResultItem(icon='images/icon.png',
name='Item %s' % i,
description='Item description %s' % i,
on_enter=HideWindowAction()))
return RenderResultListAction(items)
if __name__ == '__main__':
DemoExtension().run()
Enter fullscreen mode Exit fullscreen mode
6. Now edit manifest.json
{ "required_api_version": "2", "name": "Vim Prompter", "description": "Vim cheatsheet helper", "developer_name": "David Robert Lewis", "icon": "images/icon.png", "options": { "query_debounce": 0.1 }, "preferences": [ { "id": "vm_kw", "type": "keyword", "name": "Vim", "description": "Search for Vim commands", "default_value": "vm" } ]
Enter fullscreen mode Exit fullscreen mode
7. Add command loading function to main.py
class VmExtension(Extension):
def load_vim_commands(self):
"""Load Vim commands from JSON file."""
package_dir = os.path.dirname(os.path.abspath(__file__))
full_path = os.path.join(package_dir, 'db', 'commands.json')
with open(full_path, 'r') as file:
return json.load(file)
def __init__(self):
super().__init__()
self.vim_commands = self.load_vim_commands()
self.subscribe(KeywordQueryEvent, KeywordQueryEventListener())
Enter fullscreen mode Exit fullscreen mode
8. Create a db folder with the following:
commands.json
example structure:
{ "categories": { "navigation": { "name": "Navigation", "patterns": [ "scroll", "jump", "goto", "position" ], "subcategories": { "cursor": { "name": "Cursor Movement", "patterns": [ "move[s]? cursor", "^[hjkl]$", "^[HJKL]$", "^[wWeEbB]$" ] },
Enter fullscreen mode Exit fullscreen mode
You can see the entire commands.json here.
9. Modify the KeywordQueryEventListener
to implement the search functionality.
class KeywordQueryEventListener(EventListener):
def on_event(self, event, extension):
query = event.get_argument() or ""
items = []
# If no query, show all commands (limited to first 8) commands_to_show = extension.vim_commands
# If there's a query, filter commands if query:
commands_to_show = [
cmd for cmd in extension.vim_commands
if query.lower() in cmd['command'].lower() or
query.lower() in cmd['description'].lower()
]
# Limit results to first 8 matches for cmd in commands_to_show[:8]:
items.append(ExtensionResultItem(
icon='images/icon.png',
name=cmd['command'],
description=f"{cmd['name']} - {cmd['description']}",
on_enter=HideWindowAction()
))
return RenderResultListAction(items)
Enter fullscreen mode Exit fullscreen mode
10. Add the URL opening functionality. We’ll need to import webbrowser and modify the on_enter action to open the Vim command URL
from ulauncher.api.shared.action.OpenUrlAction import OpenUrlAction
class KeywordQueryEventListener(EventListener):
def on_event(self, event, extension):
query = event.get_argument() or ""
items = []
commands_to_show = extension.vim_commands
if query:
commands_to_show = [
cmd for cmd in extension.vim_commands
if query.lower() in cmd['command'].lower() or
query.lower() in cmd['description'].lower()
]
for cmd in commands_to_show[:8]:
url = f"https://vim.rtorr.com/#:~:text={cmd['rtorr_description']}"
items.append(ExtensionResultItem(
icon='images/icon.png',
name=cmd['command'],
description=f"{cmd['name']} - {cmd['description']}",
on_enter=OpenUrlAction(url)
))
return RenderResultListAction(items)
Enter fullscreen mode Exit fullscreen mode
11. Key changes are:
- Added OpenUrlAction import
- Replaced HideWindowAction with OpenUrlAction
- Constructed the URL using the command’s rtorr_description
12. The full project code can be viewed here:
and the ulauncher extension here
暂无评论内容