DEV Community

loading...
Cover image for An Advanced Snippets Extension for Ulauncher

An Advanced Snippets Extension for Ulauncher

mikebarkmin profile image Mike Barkmin ・5 min read

Introduction

Last week I have discovered Ulauncher a great application launcher for Linux. Ulauncher is not your typical application launcher it is more like Alfred for macOS if you are using the right extensions.

Yes Ulauncher is more or less easily extendable if you are familiar with Python and are able to overcome the spare documentation.

In this post I will show you how my advanced snippet extension for Ulauncher works.

Examples

UUID Example

First a simple one. This is a snippet which just copies a random UUID to your clipboard. In fact every snippet will be copied to the clipboard. Currently, there is no auto typing available 😥. You can also see in this simple example that my snippet engine supports metadata in the front matter - more on that later in the implementation.

---
name: Random UUID
description: "Copies a uuid to the clipboard."
---
{{ random_uuid() }}
Enter fullscreen mode Exit fullscreen mode

UUID Snippet

YouTube Markdown Example

The second example is a more advanced one. For my note-taking in obsidian I have created a few templates which make use of the vars feature. If a var is defined in the front matter the user will get requested to provide information for this var. In the case of my YouTube markdown template the video id is requested.

---
vars:
    video_id:
        label: Youtube Video Id
icon: "icons/obsidian.png"
---
---
tags: article
created: "{{ date("now", "%Y-%m-%d") }}"
modified: "{{ date("now", "%Y-%m-%d") }}"
---

url: https://www.youtube.com/watch?v={{ vars("video_id") }}
links: [[]]

<iframe width="560" height="315" src="https://www.youtube.com/embed/{{ vars("video_id") }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Enter fullscreen mode Exit fullscreen mode

YouTube Example

Structure of a Snippet

Now you might have a feeling on what the snippet extension is capable of. In this section I will show you how a snippet can be defined.

A snippet is divided into two sections - the front matter and the content.

The Front Matter

The front matter provides meta-data to the snippet extensions. You can set four keys in the front matter:

  • name: A more readable name if not set the filename will be used.
  • description: A short description of the snippet if not set the first 40 characters of your snippet will be used.
  • icon: A custom icon to visually identify your snippet in the launcher if not set the snippet extension icon will be used.
  • vars: A dictionary of variables which the user needs to provide by filling out input fields.

Variables are very useful for working with user inputs for example the component name of a React component. They are defined in a dictionary and can have a label and a default value. For example:

vars:
    name:
        label: "Name of the component"
        default: "NewComponent"
Enter fullscreen mode Exit fullscreen mode

You will see how these can be used in the template in the following section.

The Content

This is the heart of your snippet. The content of a snippet is basically a Jinja2 Template. This allows for very awesome and playful snippets, because you can use conditionals, loops, filters and so on. For example you could return different results for different times. Maybe you want to change your email opening depending on the time:

---
vars:
    recipient: 
        label: Recipient
---
{% if date("now", "%p") == "am" %}
Good day {{ vars("recipient")|trim }}!
{% else %}
Good night {{ vars("recipient")|trim }}!
{% endif %}

Best regards,
Mike Barkmin
Enter fullscreen mode Exit fullscreen mode

Or you want to import some libraries for your React component depending on the user input. Maybe use ask if the user wants a TypeScript or JavaScript file. Your imagination is your limitation.

Filters and Globals

In the last snippet - the email one - you saw that date was used. date is a global function which is available to all snippets. We also used trim a filter which is available to all snippets.

A filter is a function which operators on a single value. In Jinja2 they can be used via the pipe "|" symbol. For example: {{ "Hello"|lower }}.

Built-in Filters

Since my snippet extension is built upon Jinja2, all built-in filters from Jinja2 are available. Additionally, I have implemented filters for common programming style guides:

  • camelcase: A title => aTitle
  • pascalcase: A title => ATitle
  • kebabcase: A title => a-title
  • snakecase: A title => a_title

Built-in Globals

Currently, there are six global function.

  • date(expression, format): Returns a formatted date. Expression can be any format supported by dateparser. Format can be any format supported by strftime
  • clipboard(): Returns the content of the clipboard
  • random_int(min, max): Returns a random int between min and max
  • random_item(list): Returns a random element of the list
  • random_uuid(): Returns a random UUID
  • vars(name): Returns the content of a user-input variable. Name should match a var key defined in the front matter.

Custom Filters and Globals

So you might see that the snippet extension is already very powerful and can accomplish quite much. But what if you could provide your own filters and globals to extend the capabilities of my snippets extension?

Fetch the current weather for your location, get the latest song played on Spotify, get an inspirational quote, a random programming meme.

All of these are just a python function away to be accessible in your snippets.

Just create a globals.py and a filters.py file in your snippets directory. Define a global dictionary and a filters dictionary and map anything you want to use. For example a custom filter to replace everything of a string with a user-provided symbol:

def replace_with_symbol(text: str, symbol: str) -> str:
    return symbol * len(text)

filters = {
    "replace_with_symbol": replace_with_symbol
}
Enter fullscreen mode Exit fullscreen mode
{{ "Hello"|replace_with_symbol("*") }}
Enter fullscreen mode Exit fullscreen mode

Results in:

*****
Enter fullscreen mode Exit fullscreen mode

Or a function to get the current temperature for your location:

import urllib.request
import json

def get_temperature(long: float, lat: float) -> float:
    with urllib.request.urlopen("https://my-weather-service.org") as url:
        data = json.loads(url.read().decode())
        return f"{data['temp']}"

globals = {
    "name": "Mike Barkmin"
    "temperature": get_temperature
}
Enter fullscreen mode Exit fullscreen mode
{{ name }}
{{ temperature() }} °C
Enter fullscreen mode Exit fullscreen mode

Results in:

Mike Barkmin
18.5 °C
Enter fullscreen mode Exit fullscreen mode

Conclusion

The possibilities of my snippet extension are endless. I know endless is not necessarily a good thing, because endless could also end in confusion and loss of focus. But I hope that I could show you a few good use-cases in this article and got your inspiration working.

If you are interested in how I work with my snippet extension, feel free to check out my public repository where I share all my snippets: https://github.com/mikebarkmin/ulauncher-snippets-files/

And of course the snippet extension is open source so feel free to dig into the source code: https://github.com/mikebarkmin/ulauncher-snippets

If you are interested in a write-up on how to implement a extension for Ulauncher or on how I implemented the snippet extension, please leave a comment 😄.

Discussion (0)

pic
Editor guide