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├── images │ └── icon.png ├── versions.json ├── manifest.json └── main.py├── images │ └── icon.png ├── versions.json ├── manifest.json └── main.py
Enter fullscreen mode Exit fullscreen mode
3. In versions.json
place the following boilerplate:
<span>[</span><span> </span><span>{</span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"commit"</span><span>:</span><span> </span><span>"master"</span><span>}</span><span> </span><span>]</span><span> </span><span>[</span><span> </span><span>{</span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"commit"</span><span>:</span><span> </span><span>"master"</span><span>}</span><span> </span><span>]</span><span> </span>[ {"required_api_version": "2", "commit": "master"} ]
Enter fullscreen mode Exit fullscreen mode
4. In manifest.json
<span>{</span><span> </span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Demo extension"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Extension Description"</span><span>,</span><span> </span><span>"developer_name"</span><span>:</span><span> </span><span>"John Doe"</span><span>,</span><span> </span><span>"icon"</span><span>:</span><span> </span><span>"images/icon.png"</span><span>,</span><span> </span><span>"options"</span><span>:</span><span> </span><span>{</span><span> </span><span>"query_debounce"</span><span>:</span><span> </span><span>0.1</span><span> </span><span>},</span><span> </span><span>"preferences"</span><span>:</span><span> </span><span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>"demo_kw"</span><span>,</span><span> </span><span>"type"</span><span>:</span><span> </span><span>"keyword"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Demo"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Demo extension"</span><span>,</span><span> </span><span>"default_value"</span><span>:</span><span> </span><span>"dm"</span><span> </span><span>}</span><span> </span><span>]</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Demo extension"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Extension Description"</span><span>,</span><span> </span><span>"developer_name"</span><span>:</span><span> </span><span>"John Doe"</span><span>,</span><span> </span><span>"icon"</span><span>:</span><span> </span><span>"images/icon.png"</span><span>,</span><span> </span><span>"options"</span><span>:</span><span> </span><span>{</span><span> </span><span>"query_debounce"</span><span>:</span><span> </span><span>0.1</span><span> </span><span>},</span><span> </span><span>"preferences"</span><span>:</span><span> </span><span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>"demo_kw"</span><span>,</span><span> </span><span>"type"</span><span>:</span><span> </span><span>"keyword"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Demo"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Demo extension"</span><span>,</span><span> </span><span>"default_value"</span><span>:</span><span> </span><span>"dm"</span><span> </span><span>}</span><span> </span><span>]</span><span> </span><span>}</span><span> </span>{ "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
<span>from</span> <span>ulauncher.api.client.Extension</span> <span>import</span> <span>Extension</span><span>from</span> <span>ulauncher.api.client.EventListener</span> <span>import</span> <span>EventListener</span><span>from</span> <span>ulauncher.api.shared.event</span> <span>import</span> <span>KeywordQueryEvent</span><span>,</span> <span>ItemEnterEvent</span><span>from</span> <span>ulauncher.api.shared.item.ExtensionResultItem</span> <span>import</span> <span>ExtensionResultItem</span><span>from</span> <span>ulauncher.api.shared.action.RenderResultListAction</span> <span>import</span> <span>RenderResultListAction</span><span>from</span> <span>ulauncher.api.shared.action.HideWindowAction</span> <span>import</span> <span>HideWindowAction</span><span>class</span> <span>DemoExtension</span><span>(</span><span>Extension</span><span>):</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span><span>super</span><span>().</span><span>__init__</span><span>()</span><span>self</span><span>.</span><span>subscribe</span><span>(</span><span>KeywordQueryEvent</span><span>,</span> <span>KeywordQueryEventListener</span><span>())</span><span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span><span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span><span>items</span> <span>=</span> <span>[]</span><span>for</span> <span>i</span> <span>in</span> <span>range</span><span>(</span><span>5</span><span>):</span><span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span><span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span><span>name</span><span>=</span><span>'</span><span>Item %s</span><span>'</span> <span>%</span> <span>i</span><span>,</span><span>description</span><span>=</span><span>'</span><span>Item description %s</span><span>'</span> <span>%</span> <span>i</span><span>,</span><span>on_enter</span><span>=</span><span>HideWindowAction</span><span>()))</span><span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span><span>if</span> <span>__name__</span> <span>==</span> <span>'</span><span>__main__</span><span>'</span><span>:</span><span>DemoExtension</span><span>().</span><span>run</span><span>()</span><span>from</span> <span>ulauncher.api.client.Extension</span> <span>import</span> <span>Extension</span> <span>from</span> <span>ulauncher.api.client.EventListener</span> <span>import</span> <span>EventListener</span> <span>from</span> <span>ulauncher.api.shared.event</span> <span>import</span> <span>KeywordQueryEvent</span><span>,</span> <span>ItemEnterEvent</span> <span>from</span> <span>ulauncher.api.shared.item.ExtensionResultItem</span> <span>import</span> <span>ExtensionResultItem</span> <span>from</span> <span>ulauncher.api.shared.action.RenderResultListAction</span> <span>import</span> <span>RenderResultListAction</span> <span>from</span> <span>ulauncher.api.shared.action.HideWindowAction</span> <span>import</span> <span>HideWindowAction</span> <span>class</span> <span>DemoExtension</span><span>(</span><span>Extension</span><span>):</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span> <span>super</span><span>().</span><span>__init__</span><span>()</span> <span>self</span><span>.</span><span>subscribe</span><span>(</span><span>KeywordQueryEvent</span><span>,</span> <span>KeywordQueryEventListener</span><span>())</span> <span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span> <span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span> <span>items</span> <span>=</span> <span>[]</span> <span>for</span> <span>i</span> <span>in</span> <span>range</span><span>(</span><span>5</span><span>):</span> <span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span><span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span> <span>name</span><span>=</span><span>'</span><span>Item %s</span><span>'</span> <span>%</span> <span>i</span><span>,</span> <span>description</span><span>=</span><span>'</span><span>Item description %s</span><span>'</span> <span>%</span> <span>i</span><span>,</span> <span>on_enter</span><span>=</span><span>HideWindowAction</span><span>()))</span> <span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span> <span>if</span> <span>__name__</span> <span>==</span> <span>'</span><span>__main__</span><span>'</span><span>:</span> <span>DemoExtension</span><span>().</span><span>run</span><span>()</span>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
<span>{</span><span> </span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Vim Prompter"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Vim cheatsheet helper"</span><span>,</span><span> </span><span>"developer_name"</span><span>:</span><span> </span><span>"David Robert Lewis"</span><span>,</span><span> </span><span>"icon"</span><span>:</span><span> </span><span>"images/icon.png"</span><span>,</span><span> </span><span>"options"</span><span>:</span><span> </span><span>{</span><span> </span><span>"query_debounce"</span><span>:</span><span> </span><span>0.1</span><span> </span><span>},</span><span> </span><span>"preferences"</span><span>:</span><span> </span><span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>"vm_kw"</span><span>,</span><span> </span><span>"type"</span><span>:</span><span> </span><span>"keyword"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Vim"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Search for Vim commands"</span><span>,</span><span> </span><span>"default_value"</span><span>:</span><span> </span><span>"vm"</span><span> </span><span>}</span><span> </span><span>]</span><span> </span><span>{</span><span> </span><span>"required_api_version"</span><span>:</span><span> </span><span>"2"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Vim Prompter"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Vim cheatsheet helper"</span><span>,</span><span> </span><span>"developer_name"</span><span>:</span><span> </span><span>"David Robert Lewis"</span><span>,</span><span> </span><span>"icon"</span><span>:</span><span> </span><span>"images/icon.png"</span><span>,</span><span> </span><span>"options"</span><span>:</span><span> </span><span>{</span><span> </span><span>"query_debounce"</span><span>:</span><span> </span><span>0.1</span><span> </span><span>},</span><span> </span><span>"preferences"</span><span>:</span><span> </span><span>[</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>"vm_kw"</span><span>,</span><span> </span><span>"type"</span><span>:</span><span> </span><span>"keyword"</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Vim"</span><span>,</span><span> </span><span>"description"</span><span>:</span><span> </span><span>"Search for Vim commands"</span><span>,</span><span> </span><span>"default_value"</span><span>:</span><span> </span><span>"vm"</span><span> </span><span>}</span><span> </span><span>]</span><span> </span>{ "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
<span>class</span> <span>VmExtension</span><span>(</span><span>Extension</span><span>):</span><span>def</span> <span>load_vim_commands</span><span>(</span><span>self</span><span>):</span><span>"""</span><span>Load Vim commands from JSON file.</span><span>"""</span><span>package_dir</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>dirname</span><span>(</span><span>os</span><span>.</span><span>path</span><span>.</span><span>abspath</span><span>(</span><span>__file__</span><span>))</span><span>full_path</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>join</span><span>(</span><span>package_dir</span><span>,</span> <span>'</span><span>db</span><span>'</span><span>,</span> <span>'</span><span>commands.json</span><span>'</span><span>)</span><span>with</span> <span>open</span><span>(</span><span>full_path</span><span>,</span> <span>'</span><span>r</span><span>'</span><span>)</span> <span>as</span> <span>file</span><span>:</span><span>return</span> <span>json</span><span>.</span><span>load</span><span>(</span><span>file</span><span>)</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span><span>super</span><span>().</span><span>__init__</span><span>()</span><span>self</span><span>.</span><span>vim_commands</span> <span>=</span> <span>self</span><span>.</span><span>load_vim_commands</span><span>()</span><span>self</span><span>.</span><span>subscribe</span><span>(</span><span>KeywordQueryEvent</span><span>,</span> <span>KeywordQueryEventListener</span><span>())</span><span>class</span> <span>VmExtension</span><span>(</span><span>Extension</span><span>):</span> <span>def</span> <span>load_vim_commands</span><span>(</span><span>self</span><span>):</span> <span>"""</span><span>Load Vim commands from JSON file.</span><span>"""</span> <span>package_dir</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>dirname</span><span>(</span><span>os</span><span>.</span><span>path</span><span>.</span><span>abspath</span><span>(</span><span>__file__</span><span>))</span> <span>full_path</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>join</span><span>(</span><span>package_dir</span><span>,</span> <span>'</span><span>db</span><span>'</span><span>,</span> <span>'</span><span>commands.json</span><span>'</span><span>)</span> <span>with</span> <span>open</span><span>(</span><span>full_path</span><span>,</span> <span>'</span><span>r</span><span>'</span><span>)</span> <span>as</span> <span>file</span><span>:</span> <span>return</span> <span>json</span><span>.</span><span>load</span><span>(</span><span>file</span><span>)</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span> <span>super</span><span>().</span><span>__init__</span><span>()</span> <span>self</span><span>.</span><span>vim_commands</span> <span>=</span> <span>self</span><span>.</span><span>load_vim_commands</span><span>()</span> <span>self</span><span>.</span><span>subscribe</span><span>(</span><span>KeywordQueryEvent</span><span>,</span> <span>KeywordQueryEventListener</span><span>())</span>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:
<span>{</span><span> </span><span>"categories"</span><span>:</span><span> </span><span>{</span><span> </span><span>"navigation"</span><span>:</span><span> </span><span>{</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Navigation"</span><span>,</span><span> </span><span>"patterns"</span><span>:</span><span> </span><span>[</span><span> </span><span>"scroll"</span><span>,</span><span> </span><span>"jump"</span><span>,</span><span> </span><span>"goto"</span><span>,</span><span> </span><span>"position"</span><span> </span><span>],</span><span> </span><span>"subcategories"</span><span>:</span><span> </span><span>{</span><span> </span><span>"cursor"</span><span>:</span><span> </span><span>{</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Cursor Movement"</span><span>,</span><span> </span><span>"patterns"</span><span>:</span><span> </span><span>[</span><span> </span><span>"move[s]? cursor"</span><span>,</span><span> </span><span>"^[hjkl]$"</span><span>,</span><span> </span><span>"^[HJKL]$"</span><span>,</span><span> </span><span>"^[wWeEbB]$"</span><span> </span><span>]</span><span> </span><span>},</span><span> </span><span>{</span><span> </span><span>"categories"</span><span>:</span><span> </span><span>{</span><span> </span><span>"navigation"</span><span>:</span><span> </span><span>{</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Navigation"</span><span>,</span><span> </span><span>"patterns"</span><span>:</span><span> </span><span>[</span><span> </span><span>"scroll"</span><span>,</span><span> </span><span>"jump"</span><span>,</span><span> </span><span>"goto"</span><span>,</span><span> </span><span>"position"</span><span> </span><span>],</span><span> </span><span>"subcategories"</span><span>:</span><span> </span><span>{</span><span> </span><span>"cursor"</span><span>:</span><span> </span><span>{</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"Cursor Movement"</span><span>,</span><span> </span><span>"patterns"</span><span>:</span><span> </span><span>[</span><span> </span><span>"move[s]? cursor"</span><span>,</span><span> </span><span>"^[hjkl]$"</span><span>,</span><span> </span><span>"^[HJKL]$"</span><span>,</span><span> </span><span>"^[wWeEbB]$"</span><span> </span><span>]</span><span> </span><span>},</span><span> </span>{ "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.
<span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span><span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span><span>query</span> <span>=</span> <span>event</span><span>.</span><span>get_argument</span><span>()</span> <span>or</span> <span>""</span><span>items</span> <span>=</span> <span>[]</span><span># If no query, show all commands (limited to first 8) </span> <span>commands_to_show</span> <span>=</span> <span>extension</span><span>.</span><span>vim_commands</span><span># If there's a query, filter commands </span> <span>if</span> <span>query</span><span>:</span><span>commands_to_show</span> <span>=</span> <span>[</span><span>cmd</span> <span>for</span> <span>cmd</span> <span>in</span> <span>extension</span><span>.</span><span>vim_commands</span><span>if</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>or</span><span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>].</span><span>lower</span><span>()</span><span>]</span><span># Limit results to first 8 matches </span> <span>for</span> <span>cmd</span> <span>in</span> <span>commands_to_show</span><span>[:</span><span>8</span><span>]:</span><span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span><span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span><span>name</span><span>=</span><span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>],</span><span>description</span><span>=</span><span>f</span><span>"</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>name</span><span>'</span><span>]</span><span>}</span><span> - </span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>]</span><span>}</span><span>"</span><span>,</span><span>on_enter</span><span>=</span><span>HideWindowAction</span><span>()</span><span>))</span><span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span><span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span> <span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span> <span>query</span> <span>=</span> <span>event</span><span>.</span><span>get_argument</span><span>()</span> <span>or</span> <span>""</span> <span>items</span> <span>=</span> <span>[]</span> <span># If no query, show all commands (limited to first 8) </span> <span>commands_to_show</span> <span>=</span> <span>extension</span><span>.</span><span>vim_commands</span> <span># If there's a query, filter commands </span> <span>if</span> <span>query</span><span>:</span> <span>commands_to_show</span> <span>=</span> <span>[</span> <span>cmd</span> <span>for</span> <span>cmd</span> <span>in</span> <span>extension</span><span>.</span><span>vim_commands</span> <span>if</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>or</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>]</span> <span># Limit results to first 8 matches </span> <span>for</span> <span>cmd</span> <span>in</span> <span>commands_to_show</span><span>[:</span><span>8</span><span>]:</span> <span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span> <span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span> <span>name</span><span>=</span><span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>],</span> <span>description</span><span>=</span><span>f</span><span>"</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>name</span><span>'</span><span>]</span><span>}</span><span> - </span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>]</span><span>}</span><span>"</span><span>,</span> <span>on_enter</span><span>=</span><span>HideWindowAction</span><span>()</span> <span>))</span> <span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span>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
<span>from</span> <span>ulauncher.api.shared.action.OpenUrlAction</span> <span>import</span> <span>OpenUrlAction</span><span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span><span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span><span>query</span> <span>=</span> <span>event</span><span>.</span><span>get_argument</span><span>()</span> <span>or</span> <span>""</span><span>items</span> <span>=</span> <span>[]</span><span>commands_to_show</span> <span>=</span> <span>extension</span><span>.</span><span>vim_commands</span><span>if</span> <span>query</span><span>:</span><span>commands_to_show</span> <span>=</span> <span>[</span><span>cmd</span> <span>for</span> <span>cmd</span> <span>in</span> <span>extension</span><span>.</span><span>vim_commands</span><span>if</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>or</span><span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>].</span><span>lower</span><span>()</span><span>]</span><span>for</span> <span>cmd</span> <span>in</span> <span>commands_to_show</span><span>[:</span><span>8</span><span>]:</span><span>url</span> <span>=</span> <span>f</span><span>"</span><span>https://vim.rtorr.com/#:~:text=</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>rtorr_description</span><span>'</span><span>]</span><span>}</span><span>"</span><span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span><span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span><span>name</span><span>=</span><span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>],</span><span>description</span><span>=</span><span>f</span><span>"</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>name</span><span>'</span><span>]</span><span>}</span><span> - </span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>]</span><span>}</span><span>"</span><span>,</span><span>on_enter</span><span>=</span><span>OpenUrlAction</span><span>(</span><span>url</span><span>)</span><span>))</span><span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span><span>from</span> <span>ulauncher.api.shared.action.OpenUrlAction</span> <span>import</span> <span>OpenUrlAction</span> <span>class</span> <span>KeywordQueryEventListener</span><span>(</span><span>EventListener</span><span>):</span> <span>def</span> <span>on_event</span><span>(</span><span>self</span><span>,</span> <span>event</span><span>,</span> <span>extension</span><span>):</span> <span>query</span> <span>=</span> <span>event</span><span>.</span><span>get_argument</span><span>()</span> <span>or</span> <span>""</span> <span>items</span> <span>=</span> <span>[]</span> <span>commands_to_show</span> <span>=</span> <span>extension</span><span>.</span><span>vim_commands</span> <span>if</span> <span>query</span><span>:</span> <span>commands_to_show</span> <span>=</span> <span>[</span> <span>cmd</span> <span>for</span> <span>cmd</span> <span>in</span> <span>extension</span><span>.</span><span>vim_commands</span> <span>if</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>or</span> <span>query</span><span>.</span><span>lower</span><span>()</span> <span>in</span> <span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>].</span><span>lower</span><span>()</span> <span>]</span> <span>for</span> <span>cmd</span> <span>in</span> <span>commands_to_show</span><span>[:</span><span>8</span><span>]:</span> <span>url</span> <span>=</span> <span>f</span><span>"</span><span>https://vim.rtorr.com/#:~:text=</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>rtorr_description</span><span>'</span><span>]</span><span>}</span><span>"</span> <span>items</span><span>.</span><span>append</span><span>(</span><span>ExtensionResultItem</span><span>(</span> <span>icon</span><span>=</span><span>'</span><span>images/icon.png</span><span>'</span><span>,</span> <span>name</span><span>=</span><span>cmd</span><span>[</span><span>'</span><span>command</span><span>'</span><span>],</span> <span>description</span><span>=</span><span>f</span><span>"</span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>name</span><span>'</span><span>]</span><span>}</span><span> - </span><span>{</span><span>cmd</span><span>[</span><span>'</span><span>description</span><span>'</span><span>]</span><span>}</span><span>"</span><span>,</span> <span>on_enter</span><span>=</span><span>OpenUrlAction</span><span>(</span><span>url</span><span>)</span> <span>))</span> <span>return</span> <span>RenderResultListAction</span><span>(</span><span>items</span><span>)</span>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
暂无评论内容