Develop a ulauncher extension with a command database

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:

ulauncher-vim repo

and the ulauncher extension here

References

  1. https://dev.to/brpaz/an-introduction-to-ulauncher-extension-development-1m69
  2. https://ext.ulauncher.io/-/github-ubuntupunk-ulauncher-vim

原文链接:Develop a ulauncher extension with a command database

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
Life is like a cup of tea. It won't be bitter for a lifetime but for a short while anyway.
人生就像一杯茶,不会苦一辈子,但总会苦一阵子
评论 抢沙发

请登录后发表评论

    暂无评论内容